fetch v4 lists from api

This commit is contained in:
Owen LeJeune
2023-03-16 20:49:48 -04:00
parent 69c7e387b5
commit 7b1fd3136f
12 changed files with 192 additions and 81 deletions

View File

@@ -1,6 +1,7 @@
package com.owenlejeune.tvtime package com.owenlejeune.tvtime
import android.os.Bundle import android.os.Bundle
import android.widget.Toast
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.animation.rememberSplineBasedDecay import androidx.compose.animation.rememberSplineBasedDecay
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
@@ -70,9 +71,14 @@ class MainActivity : MonetCompatActivity() {
setContent { setContent {
AppKeyboardFocusManager() AppKeyboardFocusManager()
TVTimeTheme(monetCompat = monet) { TVTimeTheme(monetCompat = monet) {
val windowSize = rememberWindowSizeClass()
val appNavController = rememberNavController() val appNavController = rememberNavController()
Box { Box {
MainNavigationRoutes(appNavController = appNavController, mainNavStartRoute = mainNavStartRoute) MainNavigationRoutes(
appNavController = appNavController,
mainNavStartRoute = mainNavStartRoute,
windowSize = windowSize
)
} }
} }
} }
@@ -83,10 +89,9 @@ class MainActivity : MonetCompatActivity() {
private fun AppScaffold( private fun AppScaffold(
appNavController: NavHostController, appNavController: NavHostController,
mainNavStartRoute: String = BottomNavItem.SortedItems[0].route, mainNavStartRoute: String = BottomNavItem.SortedItems[0].route,
windowSize: WindowSizeClass,
preferences: AppPreferences = get(AppPreferences::class.java) preferences: AppPreferences = get(AppPreferences::class.java)
) { ) {
val windowSize = rememberWindowSizeClass()
val navController = rememberNavController() val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState() val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route val currentRoute = navBackStackEntry?.destination?.route
@@ -363,11 +368,16 @@ class MainActivity : MonetCompatActivity() {
startDestination: String = MainNavItem.MainView.route, startDestination: String = MainNavItem.MainView.route,
mainNavStartRoute: String = MainNavItem.Items[0].route, mainNavStartRoute: String = MainNavItem.Items[0].route,
appNavController: NavHostController, appNavController: NavHostController,
windowSize: WindowSizeClass,
preferences: AppPreferences = get(AppPreferences::class.java) preferences: AppPreferences = get(AppPreferences::class.java)
) { ) {
NavHost(navController = appNavController, startDestination = startDestination) { NavHost(navController = appNavController, startDestination = startDestination) {
composable(MainNavItem.MainView.route) { composable(MainNavItem.MainView.route) {
AppScaffold(appNavController = appNavController, mainNavStartRoute = mainNavStartRoute) AppScaffold(
appNavController = appNavController,
mainNavStartRoute = mainNavStartRoute,
windowSize = windowSize
)
} }
composable( composable(
MainNavItem.DetailView.route.plus("/{${NavConstants.TYPE_KEY}}/{${NavConstants.ID_KEY}}"), MainNavItem.DetailView.route.plus("/{${NavConstants.TYPE_KEY}}/{${NavConstants.ID_KEY}}"),
@@ -378,17 +388,27 @@ class MainActivity : MonetCompatActivity() {
) { navBackStackEntry -> ) { navBackStackEntry ->
val args = navBackStackEntry.arguments val args = navBackStackEntry.arguments
val mediaType = args?.getSerializable(NavConstants.TYPE_KEY) as MediaViewType val mediaType = args?.getSerializable(NavConstants.TYPE_KEY) as MediaViewType
if (mediaType != MediaViewType.PERSON) {
MediaDetailView( when (mediaType) {
appNavController = appNavController, MediaViewType.PERSON -> {
itemId = args.getInt(NavConstants.ID_KEY), PersonDetailView(
type = mediaType appNavController = appNavController,
) personId = args.getInt(NavConstants.ID_KEY)
} else { )
PersonDetailView( }
appNavController = appNavController, MediaViewType.LIST -> {
personId = args.getInt(NavConstants.ID_KEY) LocalContext.current.let {
) Toast.makeText(it, "It's a list!", Toast.LENGTH_SHORT).show()
}
}
else -> {
MediaDetailView(
appNavController = appNavController,
itemId = args.getInt(NavConstants.ID_KEY),
type = mediaType,
windowSize = windowSize
)
}
} }
} }
composable( composable(

View File

@@ -9,34 +9,35 @@ import com.owenlejeune.tvtime.api.tmdb.api.v4.model.V4RatedTv
import retrofit2.Response import retrofit2.Response
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Path import retrofit2.http.Path
import retrofit2.http.Query
interface AccountV4Api { interface AccountV4Api {
@GET("account/{account_id}/lists") @GET("account/{account_id}/lists")
suspend fun getLists(@Path("account_id") accountId: String, page: Int = 1): Response<V4AccountResponse<V4AccountList>> suspend fun getLists(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<V4AccountResponse<V4AccountList>>
@GET("account/{account_id}/movie/favorites") @GET("account/{account_id}/movie/favorites")
suspend fun getFavoriteMovies(@Path("account_id") accountId: String, page: Int = 1): Response<V4AccountResponse<FavoriteMovie>> suspend fun getFavoriteMovies(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<V4AccountResponse<FavoriteMovie>>
@GET("account/{account_id}/tv/favorites") @GET("account/{account_id}/tv/favorites")
suspend fun getFavoriteTvShows(@Path("account_id") accountId: String, page: Int = 1): Response<V4AccountResponse<FavoriteTvSeries>> suspend fun getFavoriteTvShows(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<V4AccountResponse<FavoriteTvSeries>>
@GET("account/{account_id}/movie/recommendations") @GET("account/{account_id}/movie/recommendations")
suspend fun getMovieRecommendations(@Path("account_id") accountId: String, page: Int = 1): Response<V4AccountResponse<FavoriteMovie>> suspend fun getMovieRecommendations(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<V4AccountResponse<FavoriteMovie>>
@GET("account/{account_id}/tv/recommendations") @GET("account/{account_id}/tv/recommendations")
suspend fun getTvShowRecommendations(@Path("account_id") accountId: String, page: Int = 1): Response<V4AccountResponse<FavoriteTvSeries>> suspend fun getTvShowRecommendations(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<V4AccountResponse<FavoriteTvSeries>>
@GET("account/{account_id}/movie/watchlist") @GET("account/{account_id}/movie/watchlist")
suspend fun getMovieWatchlist(@Path("account_id") accountId: String, page: Int = 1): Response<V4AccountResponse<FavoriteMovie>> suspend fun getMovieWatchlist(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<V4AccountResponse<FavoriteMovie>>
@GET("account/{account_id}/tv/watchlist") @GET("account/{account_id}/tv/watchlist")
suspend fun getTvShowWatchlist(@Path("account_id") accountId: String, page: Int = 1): Response<V4AccountResponse<FavoriteTvSeries>> suspend fun getTvShowWatchlist(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<V4AccountResponse<FavoriteTvSeries>>
@GET("account/{account_id}/movie/rated") @GET("account/{account_id}/movie/rated")
suspend fun getRatedMovies(@Path("account_id") accountId: String, page: Int = 1): Response<V4AccountResponse<V4RatedMovie>> suspend fun getRatedMovies(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<V4AccountResponse<V4RatedMovie>>
@GET("account/{account_id}/tv/rated") @GET("account/{account_id}/tv/rated")
suspend fun getRatedTvShows(@Path("account_id") accountId: String, page: Int = 1): Response<V4AccountResponse<V4RatedTv>> suspend fun getRatedTvShows(@Path("account_id") accountId: String, @Query("page") page: Int = 1): Response<V4AccountResponse<V4RatedTv>>
} }

View File

@@ -8,7 +8,7 @@ class ListV4Service {
private val service by lazy { TmdbClient().createV4ListService() } private val service by lazy { TmdbClient().createV4ListService() }
suspend fun getLists(listId: Int, apiKey: String, page: Int = 1): Response<MediaList> { suspend fun getList(listId: Int, apiKey: String, page: Int = 1): Response<MediaList> {
return service.getList(listId, apiKey, page) return service.getList(listId, apiKey, page)
} }

View File

@@ -6,10 +6,12 @@ import com.google.gson.JsonDeserializer
import com.owenlejeune.tvtime.BuildConfig import com.owenlejeune.tvtime.BuildConfig
import com.owenlejeune.tvtime.api.* import com.owenlejeune.tvtime.api.*
import com.owenlejeune.tvtime.api.tmdb.TmdbClient import com.owenlejeune.tvtime.api.tmdb.TmdbClient
import com.owenlejeune.tvtime.api.tmdb.api.v3.AccountService
import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.KnownForDeserializer import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.KnownForDeserializer
import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.SortableSearchResultDeserializer import com.owenlejeune.tvtime.api.tmdb.api.v3.deserializer.SortableSearchResultDeserializer
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KnownFor import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KnownFor
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SortableSearchResult import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SortableSearchResult
import com.owenlejeune.tvtime.api.tmdb.api.v4.AccountV4Service
import com.owenlejeune.tvtime.api.tmdb.api.v4.deserializer.ListItemDeserializer import com.owenlejeune.tvtime.api.tmdb.api.v4.deserializer.ListItemDeserializer
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListItem import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListItem
import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.preferences.AppPreferences
@@ -33,6 +35,9 @@ val networkModule = module {
single { get<TmdbClient>().createSearchService() } single { get<TmdbClient>().createSearchService() }
single { get<TmdbClient>().createTvService() } single { get<TmdbClient>().createTvService() }
single { AccountService() }
single { AccountV4Service() }
single<Map<Class<*>, JsonDeserializer<*>>> { single<Map<Class<*>, JsonDeserializer<*>>> {
mapOf( mapOf(
ListItem::class.java to ListItemDeserializer(), ListItem::class.java to ListItemDeserializer(),

View File

@@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.* import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.V4AccountList
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
import com.owenlejeune.tvtime.ui.screens.main.AccountTabContent import com.owenlejeune.tvtime.ui.screens.main.AccountTabContent
import com.owenlejeune.tvtime.utils.ResourceUtils import com.owenlejeune.tvtime.utils.ResourceUtils
@@ -31,7 +32,10 @@ sealed class AccountTabNavItem(
get() = listOf(RatedMovies, RatedTvShows, RatedTvEpisodes) get() = listOf(RatedMovies, RatedTvShows, RatedTvEpisodes)
val AuthorizedItems val AuthorizedItems
get() = listOf(RatedMovies, RatedTvShows, RatedTvEpisodes, FavoriteMovies, FavoriteTvShows, MovieWatchlist, TvWatchlist).filter { it.ordinal > -1 }.sortedBy { it.ordinal } get() = listOf(
RatedMovies, RatedTvShows, RatedTvEpisodes, FavoriteMovies, FavoriteTvShows,
MovieWatchlist, TvWatchlist, UserLists
).filter { it.ordinal > -1 }.sortedBy { it.ordinal }
} }
object RatedMovies: AccountTabNavItem( object RatedMovies: AccountTabNavItem(
@@ -39,7 +43,8 @@ sealed class AccountTabNavItem(
"rated_movies_route", "rated_movies_route",
R.string.no_rated_movies, R.string.no_rated_movies,
MediaViewType.MOVIE, MediaViewType.MOVIE,
screenContent, { SessionManager.currentSession?.ratedMovies ?: emptyList() }, screenContent,
{ SessionManager.currentSession?.ratedMovies ?: emptyList() },
RatedMovie::class, RatedMovie::class,
0 0
) )
@@ -48,7 +53,8 @@ sealed class AccountTabNavItem(
"rated_shows_route", "rated_shows_route",
R.string.no_rated_tv, R.string.no_rated_tv,
MediaViewType.TV, MediaViewType.TV,
screenContent, { SessionManager.currentSession?.ratedTvShows ?: emptyList() }, screenContent,
{ SessionManager.currentSession?.ratedTvShows ?: emptyList() },
RatedTv::class, RatedTv::class,
1 1
) )
@@ -57,16 +63,18 @@ sealed class AccountTabNavItem(
"rated_episodes_route", "rated_episodes_route",
R.string.no_rated_episodes, R.string.no_rated_episodes,
MediaViewType.EPISODE, MediaViewType.EPISODE,
screenContent, { SessionManager.currentSession?.ratedTvEpisodes ?: emptyList() }, screenContent,
{ SessionManager.currentSession?.ratedTvEpisodes ?: emptyList() },
RatedEpisode::class, RatedEpisode::class,
2 -1 //2
) )
object FavoriteMovies: AccountTabNavItem( object FavoriteMovies: AccountTabNavItem(
R.string.nav_favorite_movies_title, R.string.nav_favorite_movies_title,
"favorite_movies_route", "favorite_movies_route",
R.string.no_favorite_movies, R.string.no_favorite_movies,
MediaViewType.MOVIE, MediaViewType.MOVIE,
screenContent, { SessionManager.currentSession?.favoriteMovies ?: emptyList() }, screenContent,
{ SessionManager.currentSession?.favoriteMovies ?: emptyList() },
FavoriteMovie::class, FavoriteMovie::class,
3 3
) )
@@ -75,7 +83,8 @@ sealed class AccountTabNavItem(
"favorite_shows_route", "favorite_shows_route",
R.string.no_favorite_tv, R.string.no_favorite_tv,
MediaViewType.TV, MediaViewType.TV,
screenContent, { SessionManager.currentSession?.favoriteTvShows ?: emptyList() }, screenContent,
{ SessionManager.currentSession?.favoriteTvShows ?: emptyList() },
FavoriteTvSeries::class, FavoriteTvSeries::class,
4 4
) )
@@ -84,7 +93,8 @@ sealed class AccountTabNavItem(
"movie_watchlist_route", "movie_watchlist_route",
R.string.no_watchlist_movies, R.string.no_watchlist_movies,
MediaViewType.MOVIE, MediaViewType.MOVIE,
screenContent, { SessionManager.currentSession?.movieWatchlist ?: emptyList() }, screenContent,
{ SessionManager.currentSession?.movieWatchlist ?: emptyList() },
WatchlistMovie::class, WatchlistMovie::class,
5 5
) )
@@ -93,10 +103,22 @@ sealed class AccountTabNavItem(
"tv_watchlist_route", "tv_watchlist_route",
R.string.no_watchlist_tv, R.string.no_watchlist_tv,
MediaViewType.TV, MediaViewType.TV,
screenContent, { SessionManager.currentSession?.tvWatchlist ?: emptyList() }, screenContent,
{ SessionManager.currentSession?.tvWatchlist ?: emptyList() },
WatchlistTvSeries::class, WatchlistTvSeries::class,
6 6
) )
object UserLists: AccountTabNavItem(
R.string.nav_user_lists_title,
"user_lists_route",
R.string.no_lists,
MediaViewType.LIST,
screenContent,
{ SessionManager.currentSession?.accountLists ?: emptyList() },
V4AccountList::class,
0//7
)
} }
private val screenContent: AccountNavComposableFun = { noContentText, appNavController, mediaViewType, listFetchFun, clazz -> private val screenContent: AccountNavComposableFun = { noContentText, appNavController, mediaViewType, listFetchFun, clazz ->

View File

@@ -37,6 +37,8 @@ import com.google.accompanist.pager.PagerState
import com.google.accompanist.pager.rememberPagerState import com.google.accompanist.pager.rememberPagerState
import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.* import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.V4AccountList
import com.owenlejeune.tvtime.extensions.unlessEmpty
import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.components.RoundedLetterImage import com.owenlejeune.tvtime.ui.components.RoundedLetterImage
import com.owenlejeune.tvtime.ui.components.SignInDialog import com.owenlejeune.tvtime.ui.components.SignInDialog
@@ -238,6 +240,19 @@ fun <T: Any> AccountTabContent(
description = item.overview description = item.overview
) )
} }
V4AccountList::class -> {
val item = contentItems[i] as V4AccountList
MediaItemRow(
appNavController = appNavController,
mediaViewType = mediaViewType,
id = item.id,
name = item.name,
date = item.createdAt,
description = item.description.unlessEmpty(stringResource(R.string.no_description_provided)),
posterPath = TmdbUtils.getFullPosterPath(item.posterPath),
backdropPath = TmdbUtils.getFullBackdropPath(item.backdropPath)
)
}
} }
} }
} }

View File

@@ -36,6 +36,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.DetailService
import com.owenlejeune.tvtime.api.tmdb.api.v3.MoviesService import com.owenlejeune.tvtime.api.tmdb.api.v3.MoviesService
import com.owenlejeune.tvtime.api.tmdb.api.v3.TvService import com.owenlejeune.tvtime.api.tmdb.api.v3.TvService
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.* import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
import com.owenlejeune.tvtime.extensions.WindowSizeClass
import com.owenlejeune.tvtime.extensions.listItems import com.owenlejeune.tvtime.extensions.listItems
import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.preferences.AppPreferences
import com.owenlejeune.tvtime.ui.components.* import com.owenlejeune.tvtime.ui.components.*
@@ -57,6 +58,7 @@ fun MediaDetailView(
appNavController: NavController, appNavController: NavController,
itemId: Int?, itemId: Int?,
type: MediaViewType, type: MediaViewType,
windowSize: WindowSizeClass,
preferences: AppPreferences = KoinJavaComponent.get(AppPreferences::class.java) preferences: AppPreferences = KoinJavaComponent.get(AppPreferences::class.java)
) { ) {
val service = when (type) { val service = when (type) {
@@ -104,51 +106,76 @@ fun MediaDetailView(
} }
) { innerPadding -> ) { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) { Box(modifier = Modifier.padding(innerPadding)) {
Column( Row(
modifier = Modifier modifier = Modifier
.background(color = MaterialTheme.colorScheme.background) .background(color = MaterialTheme.colorScheme.background)
.verticalScroll(state = rememberScrollState())
.padding(bottom = 16.dp), .padding(bottom = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp) horizontalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
val images = remember { mutableStateOf<ImageCollection?>(null) }
itemId?.let {
if (preferences.showBackdropGallery && images.value == null) {
fetchImages(itemId, service, images)
}
}
DetailHeader(
posterUrl = TmdbUtils.getFullPosterPath(mediaItem.value?.posterPath),
posterContentDescription = mediaItem.value?.title,
backdropUrl = TmdbUtils.getFullBackdropPath(mediaItem.value?.backdropPath),
rating = mediaItem.value?.voteAverage?.let { it / 10 },
imageCollection = images.value
)
Column( Column(
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier
.background(color = MaterialTheme.colorScheme.background)
.weight(1f)
.verticalScroll(state = rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
if (type == MediaViewType.MOVIE) { val images = remember { mutableStateOf<ImageCollection?>(null) }
MiscMovieDetails(mediaItem = mediaItem, service as MoviesService) itemId?.let {
} else { if (preferences.showBackdropGallery && images.value == null) {
MiscTvDetails(mediaItem = mediaItem, service as TvService) fetchImages(itemId, service, images)
}
} }
ActionsView(itemId = itemId, type = type, service = service) DetailHeader(
posterUrl = TmdbUtils.getFullPosterPath(mediaItem.value?.posterPath),
posterContentDescription = mediaItem.value?.title,
backdropUrl = TmdbUtils.getFullBackdropPath(mediaItem.value?.backdropPath),
rating = mediaItem.value?.voteAverage?.let { it / 10 },
imageCollection = images.value
)
OverviewCard(itemId = itemId, mediaItem = mediaItem, service = service) Column(
modifier = Modifier.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
if (type == MediaViewType.MOVIE) {
MiscMovieDetails(mediaItem = mediaItem, service as MoviesService)
} else {
MiscTvDetails(mediaItem = mediaItem, service as TvService)
}
CastCard(itemId = itemId, service = service, appNavController = appNavController) ActionsView(itemId = itemId, type = type, service = service)
SimilarContentCard(itemId = itemId, service = service, mediaType = type, appNavController = appNavController) OverviewCard(itemId = itemId, mediaItem = mediaItem, service = service)
VideosCard(itemId = itemId, service = service) CastCard(itemId = itemId, service = service, appNavController = appNavController)
AdditionalDetailsCard(itemId = itemId, mediaItem = mediaItem, service = service, type = type) SimilarContentCard(itemId = itemId, service = service, mediaType = type, appNavController = appNavController)
ReviewsCard(itemId = itemId, service = service) VideosCard(itemId = itemId, service = service)
AdditionalDetailsCard(itemId = itemId, mediaItem = mediaItem, service = service, type = type)
if (windowSize != WindowSizeClass.Expanded) {
ReviewsCard(itemId = itemId, service = service)
}
}
Spacer(modifier = Modifier.height(16.dp))
}
if (windowSize == WindowSizeClass.Expanded) {
Column(
modifier = Modifier
.background(color = MaterialTheme.colorScheme.background)
.weight(1f)
.padding(bottom = 16.dp)
.verticalScroll(state = rememberScrollState())
) {
ReviewsCard(itemId = itemId, service = service)
Spacer(modifier = Modifier.height(16.dp))
}
} }
} }
} }
@@ -1025,7 +1052,7 @@ private fun ReviewsCard(
) { ) {
val reviews = reviewsResponse.value?.results ?: emptyList() val reviews = reviewsResponse.value?.results ?: emptyList()
if (reviews.isNotEmpty()) { if (reviews.isNotEmpty()) {
reviews.reversed().forEach { review -> reviews.reversed().forEachIndexed { index, review ->
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -1088,10 +1115,12 @@ private fun ReviewsCard(
) )
} }
} }
Divider( if (index != reviews.size - 1) {
color = MaterialTheme.colorScheme.secondary, Divider(
modifier = Modifier.padding(vertical = 12.dp) color = MaterialTheme.colorScheme.secondary,
) modifier = Modifier.padding(vertical = 12.dp)
)
}
} }
} else { } else {
Text( Text(

View File

@@ -10,7 +10,8 @@ enum class MediaViewType {
@SerializedName("person") @SerializedName("person")
PERSON, PERSON,
EPISODE, EPISODE,
MIXED; MIXED,
LIST;
companion object { companion object {
const val JSON_KEY = "media_type" const val JSON_KEY = "media_type"

View File

@@ -2,6 +2,7 @@ package com.owenlejeune.tvtime.ui.screens.main
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.* import androidx.compose.material3.*
@@ -11,6 +12,7 @@ import androidx.compose.ui.draw.blur
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -19,6 +21,7 @@ import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension import androidx.constraintlayout.compose.Dimension
import androidx.navigation.NavController import androidx.navigation.NavController
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.ui.navigation.MainNavItem import com.owenlejeune.tvtime.ui.navigation.MainNavItem
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -63,7 +66,7 @@ fun MediaResultCard(
Box(modifier = Modifier Box(modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(Color.Black.copy(alpha = 0.7f)) .background(Color.Black.copy(alpha = 0.4f))
.blur(radius = 10.dp) .blur(radius = 10.dp)
) )
} }
@@ -83,8 +86,9 @@ fun MediaResultCard(
bottom.linkTo(parent.bottom) bottom.linkTo(parent.bottom)
height = Dimension.fillToConstraints height = Dimension.fillToConstraints
} }
.aspectRatio(0.7f)
.clip(RoundedCornerShape(10.dp)), .clip(RoundedCornerShape(10.dp)),
model = posterPath, model = posterPath ?: R.drawable.placeholder,
contentDescription = title contentDescription = title
) )
@@ -101,9 +105,10 @@ fun MediaResultCard(
height = Dimension.matchParent height = Dimension.matchParent
} }
) { ) {
val textColor = backdropPath?.let { Color.White } ?: if (isSystemInDarkTheme()) Color.White else Color.Black
Text( Text(
text = title, text = title,
color = MaterialTheme.colorScheme.onBackground, color = textColor,
fontSize = 18.sp, fontSize = 18.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
@@ -111,7 +116,7 @@ fun MediaResultCard(
additionalDetails.forEach { additionalDetails.forEach {
Text( Text(
text = it, text = it,
color = MaterialTheme.colorScheme.onBackground, color = textColor,
fontSize = 14.sp, fontSize = 14.sp,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )

View File

@@ -1,5 +1,6 @@
package com.owenlejeune.tvtime.utils package com.owenlejeune.tvtime.utils
import android.accounts.Account
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
@@ -10,11 +11,15 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.AccountService
import com.owenlejeune.tvtime.api.tmdb.api.v3.AuthenticationService import com.owenlejeune.tvtime.api.tmdb.api.v3.AuthenticationService
import com.owenlejeune.tvtime.api.tmdb.api.v3.GuestSessionService import com.owenlejeune.tvtime.api.tmdb.api.v3.GuestSessionService
import com.owenlejeune.tvtime.api.tmdb.TmdbClient import com.owenlejeune.tvtime.api.tmdb.TmdbClient
import com.owenlejeune.tvtime.api.tmdb.api.v3.AccountApi
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.* import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
import com.owenlejeune.tvtime.api.tmdb.api.v4.AccountV4Api
import com.owenlejeune.tvtime.api.tmdb.api.v4.AccountV4Service
import com.owenlejeune.tvtime.api.tmdb.api.v4.AuthenticationV4Service import com.owenlejeune.tvtime.api.tmdb.api.v4.AuthenticationV4Service
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthAccessBody import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthAccessBody
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthDeleteBody import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthDeleteBody
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthRequestBody import com.owenlejeune.tvtime.api.tmdb.api.v4.model.AuthRequestBody
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.V4AccountList
import com.owenlejeune.tvtime.preferences.AppPreferences import com.owenlejeune.tvtime.preferences.AppPreferences
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -90,8 +95,8 @@ object SessionManager: KoinComponent {
accessToken = values.accessToken, accessToken = values.accessToken,
accountId = values.accountId accountId = values.accountId
) )
session.initialize()
_currentSession = session _currentSession = session
session.initialize()
} }
} }
} }
@@ -207,8 +212,8 @@ object SessionManager: KoinComponent {
val accountDetails: AccountDetails? val accountDetails: AccountDetails?
get() = _accountDetails get() = _accountDetails
protected open var _accountLists: List<AccountList> = emptyList() protected open var _accountLists: List<V4AccountList> = emptyList()
val accountLists: List<AccountList> val accountLists: List<V4AccountList>
get() = _accountLists get() = _accountLists
protected open var _favoriteMovies: List<FavoriteMovie> = emptyList() protected open var _favoriteMovies: List<FavoriteMovie> = emptyList()
@@ -302,7 +307,8 @@ object SessionManager: KoinComponent {
accessToken: String = "", accessToken: String = "",
accountId: String = "" accountId: String = ""
): Session(sessionId, true, accessToken, accountId) { ): Session(sessionId, true, accessToken, accountId) {
private val service by lazy { AccountService() } private val service: AccountService by inject()
private val serviceV4: AccountV4Service by inject()
override suspend fun initialize() { override suspend fun initialize() {
refresh() refresh()
@@ -325,7 +331,7 @@ object SessionManager: KoinComponent {
private suspend fun refreshWithAccountId(accountId: Int, changed: Array<Changed> = Changed.All) { private suspend fun refreshWithAccountId(accountId: Int, changed: Array<Changed> = Changed.All) {
if (changed.contains(Changed.Lists)) { if (changed.contains(Changed.Lists)) {
service.getLists(accountId).apply { serviceV4.getLists(preferences.authorizedSessionValues?.accountId ?: "").apply {
if (isSuccessful) { if (isSuccessful) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
_accountLists = body()?.results ?: _accountLists _accountLists = body()?.results ?: _accountLists

View File

@@ -15,7 +15,9 @@ object TmdbUtils {
private const val DEF_REGION = "US" private const val DEF_REGION = "US"
fun getFullPosterPath(posterPath: String?): String? { fun getFullPosterPath(posterPath: String?): String? {
return posterPath?.let { "${POSTER_BASE}${posterPath}" } return posterPath?.let {
if (posterPath.isEmpty()) null else "${POSTER_BASE}${posterPath}"
}
} }
fun getFullPosterPath(tmdbItem: TmdbItem?): String? { fun getFullPosterPath(tmdbItem: TmdbItem?): String? {
@@ -27,7 +29,9 @@ object TmdbUtils {
} }
fun getFullBackdropPath(backdropPath: String?): String? { fun getFullBackdropPath(backdropPath: String?): String? {
return backdropPath?.let { "${BACKDROP_BASE}${backdropPath}" } return backdropPath?.let {
if (backdropPath.isEmpty()) null else "${BACKDROP_BASE}${backdropPath}"
}
} }
fun getFullBackdropPath(detailItem: DetailedItem?): String? { fun getFullBackdropPath(detailItem: DetailedItem?): String? {

View File

@@ -22,6 +22,7 @@
<string name="nav_favorite_tv_show_title">Favorite TV Shows</string> <string name="nav_favorite_tv_show_title">Favorite TV Shows</string>
<string name="nav_movie_watchlist_title">Movie Watchlist</string> <string name="nav_movie_watchlist_title">Movie Watchlist</string>
<string name="nav_tv_watchlist_title">TV Watchlist</string> <string name="nav_tv_watchlist_title">TV Watchlist</string>
<string name="nav_user_lists_title">Lists</string>
<!-- Headings --> <!-- Headings -->
<string name="cast_label">Cast</string> <string name="cast_label">Cast</string>
@@ -180,4 +181,6 @@
<string name="no_favorite_tv">No Favorite TV</string> <string name="no_favorite_tv">No Favorite TV</string>
<string name="no_watchlist_movies">No Watchlisted Movies</string> <string name="no_watchlist_movies">No Watchlisted Movies</string>
<string name="no_watchlist_tv">No Watchlisted TV</string> <string name="no_watchlist_tv">No Watchlisted TV</string>
<string name="no_lists">No Lists</string>
<string name="no_description_provided">No description provided</string>
</resources> </resources>