add trending content to home pages

This commit is contained in:
Owen LeJeune
2023-06-24 00:01:47 -04:00
parent 6fa7a27f1c
commit ef11a88a90
26 changed files with 375 additions and 97 deletions

View File

@@ -53,7 +53,7 @@ class BasePagingSource<T: Any, S>(
nextKey = if (results.isEmpty()) { null } else { page + 1}
)
} else {
Toast.makeText(context, context.getString(R.string.no_result_found), Toast.LENGTH_SHORT).show()
// Toast.makeText(context, context.getString(R.string.no_result_found), Toast.LENGTH_SHORT).show()
LoadResult.Invalid()
}
} catch (e: Exception) {

View File

@@ -1,6 +1,7 @@
package com.owenlejeune.tvtime.api.tmdb.api.v3
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
import com.owenlejeune.tvtime.utils.types.TimeWindow
import retrofit2.Response
import retrofit2.http.*
@@ -67,4 +68,7 @@ interface MoviesApi {
@GET("discover/movie")
suspend fun discover(@Query("with_keywords") keywords: String? = null, @Query("page") page: Int): Response<SearchResult<SearchResultMovie>>
@GET("trending/movie/{time_window}")
suspend fun trending(@Path("time_window") timeWindow: String, @Query("page") page: Int): Response<SearchResult<SearchResultMovie>>
}

View File

@@ -28,6 +28,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Video
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviders
import com.owenlejeune.tvtime.utils.SessionManager
import com.owenlejeune.tvtime.utils.types.TimeWindow
import kotlinx.coroutines.flow.Flow
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@@ -137,6 +138,10 @@ class MoviesService: KoinComponent, DetailService, HomePageService {
}
}
suspend fun getTrending(timeWindow: TimeWindow, page: Int): Response<out SearchResult<out SearchResultMedia>> {
return movieService.trending(timeWindow.name.lowercase(), page)
}
override suspend fun discover(keywords: String?, page: Int): Response<out SearchResult<out SearchResultMedia>> {
return movieService.discover(keywords, page)
}

View File

@@ -5,6 +5,9 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ExternalIds
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePagePeopleResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PersonCreditsResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PersonImageCollection
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResult
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResultPerson
import com.owenlejeune.tvtime.utils.types.TimeWindow
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Path
@@ -36,4 +39,7 @@ interface PeopleApi {
@GET("person/{id}/external_ids")
suspend fun getExternalIds(@Path("id") id: Int): Response<ExternalIds>
@GET("trending/person/{time_window}")
suspend fun trending(@Path("time_window") timeWindow: String, @Query("page") page: Int): Response<SearchResult<SearchResultPerson>>
}

View File

@@ -12,6 +12,10 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePagePeopleResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PersonCreditsResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PersonImage
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.PersonImageCollection
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResult
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResultMedia
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResultPerson
import com.owenlejeune.tvtime.utils.types.TimeWindow
import okhttp3.internal.notify
import org.koin.core.component.KoinComponent
import retrofit2.Response
@@ -90,4 +94,8 @@ class PeopleService: KoinComponent {
return service.getPopular(page)
}
suspend fun getTrending(timeWindow: TimeWindow, page: Int): Response<SearchResult<SearchResultPerson>> {
return service.trending(timeWindow.name.lowercase(), page)
}
}

View File

@@ -1,6 +1,7 @@
package com.owenlejeune.tvtime.api.tmdb.api.v3
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.*
import com.owenlejeune.tvtime.utils.types.TimeWindow
import retrofit2.Response
import retrofit2.http.*
@@ -69,4 +70,7 @@ interface TvApi {
@GET("discover/tv")
suspend fun discover(@Query("page") page: Int, @Query("with_keywords") keywords: String? = null): Response<SearchResult<SearchResultTv>>
@GET("trending/tv/{time_window}")
suspend fun trending(@Path("time_window") timeWindow: String, @Query("page") page: Int): Response<SearchResult<SearchResultTv>>
}

View File

@@ -32,6 +32,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.VideoResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviderResponse
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviders
import com.owenlejeune.tvtime.utils.SessionManager
import com.owenlejeune.tvtime.utils.types.TimeWindow
import kotlinx.coroutines.flow.Flow
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@@ -140,6 +141,10 @@ class TvService: KoinComponent, DetailService, HomePageService {
}
}
suspend fun getTrending(timeWindow: TimeWindow, page: Int): Response<out SearchResult<out SearchResultMedia>> {
return service.trending(timeWindow.name.lowercase(), page)
}
override suspend fun discover(keywords: String?, page: Int): Response<out SearchResult<out SearchResultMedia>> {
return service.discover(page, keywords)
}

View File

@@ -12,9 +12,9 @@ abstract class SearchResultMedia(
@SerializedName("genre_ids") val genreIds: List<Int>,
@SerializedName("original_language") val originalLanguage: String,
@SerializedName("original_name", alternate = ["original_title"]) val originalName: String,
@SerializedName("poster_path") val posterPath: String?,
posterPath: String?,
type: MediaViewType,
id: Int,
name: String,
popularity: Float
): SortableSearchResult(type, popularity, id, name)
): SortableSearchResult(type, popularity, id, name, posterPath)

View File

@@ -4,10 +4,10 @@ import com.google.gson.annotations.SerializedName
import com.owenlejeune.tvtime.utils.types.MediaViewType
class SearchResultPerson(
@SerializedName("profile_path") val profilePath: String,
@SerializedName("adult") val isAdult: Boolean,
@SerializedName("known_for") val knownFor: List<KnownFor>,
profilePath: String,
id: Int,
name: String,
popularity: Float
): SortableSearchResult(MediaViewType.PERSON, popularity, id, name)
): SortableSearchResult(MediaViewType.PERSON, popularity, id, name, profilePath)

View File

@@ -6,6 +6,7 @@ import com.owenlejeune.tvtime.utils.types.MediaViewType
abstract class SortableSearchResult(
@SerializedName("media_type") val mediaType: MediaViewType,
@SerializedName("popularity") val popularity: Float,
@SerializedName("id") val id: Int,
@SerializedName("name", alternate = ["title"]) val name: String
): Searchable
id: Int,
name: String,
posterPath: String?
): TmdbItem(id, posterPath, name), Searchable

View File

@@ -4,6 +4,6 @@ import com.google.gson.annotations.SerializedName
abstract class TmdbItem(
@SerializedName("id") val id: Int,
@SerializedName("poster_path") val posterPath: String?,
@SerializedName("poster_path", alternate = ["profile_path"]) val posterPath: String?,
@SerializedName("name", alternate = ["title"]) val title: String
)

View File

@@ -52,11 +52,13 @@ private val POSTER_HEIGHT = 180.dp
@Composable
fun PagingPosterGrid(
modifier: Modifier = Modifier,
lazyPagingItems: LazyPagingItems<TmdbItem>?,
onClick: (id: Int) -> Unit = {}
) {
lazyPagingItems?.let {
LazyVerticalGrid(
modifier = modifier,
columns = GridCells.Adaptive(minSize = POSTER_WIDTH),
contentPadding = PaddingValues(8.dp),
horizontalArrangement = Arrangement.SpaceBetween

View File

@@ -1117,15 +1117,18 @@ fun SelectableTextChip(
selected: Boolean,
onSelected: () -> Unit,
text: String,
modifier: Modifier = Modifier,
selectedColor: Color = MaterialTheme.colorScheme.secondary,
unselectedColor: Color = MaterialTheme.colorScheme.surfaceVariant
) {
Box(
modifier = Modifier
.clip(RoundedCornerShape(percent = 25))
.border(width = 1.dp, color = selectedColor, shape = RoundedCornerShape(percent = 25))
.background(color = if(selected) selectedColor else unselectedColor)
.clickable(onClick = onSelected)
modifier = modifier.then(
Modifier
.clip(RoundedCornerShape(percent = 25))
.border(width = 1.dp, color = selectedColor, shape = RoundedCornerShape(percent = 25))
.background(color = if(selected) selectedColor else unselectedColor)
.clickable(onClick = onSelected)
)
) {
Text(
text = text,

View File

@@ -40,8 +40,8 @@ import com.owenlejeune.tvtime.ui.components.AccountIcon
import com.owenlejeune.tvtime.ui.components.MediaResultCard
import com.owenlejeune.tvtime.ui.components.PagingPosterGrid
import com.owenlejeune.tvtime.ui.components.ScrollableTabs
import com.owenlejeune.tvtime.ui.navigation.AccountTabNavItem
import com.owenlejeune.tvtime.ui.navigation.AppNavItem
import com.owenlejeune.tvtime.ui.screens.tabs.AccountTabNavItem
import com.owenlejeune.tvtime.ui.viewmodel.AccountViewModel
import com.owenlejeune.tvtime.utils.SessionManager
import com.owenlejeune.tvtime.utils.TmdbUtils

View File

@@ -79,7 +79,7 @@ fun KeywordResultsScreen(
id = result.id,
backdropPath = TmdbUtils.getFullBackdropPath(result.backdropPath),
posterPath = TmdbUtils.getFullPosterPath(result.posterPath),
title = result.name,
title = result.title,
additionalDetails = listOf(result.overview)
)
}

View File

@@ -204,17 +204,19 @@ fun PersonDetailScreen(
@Composable
private fun BiographyCard(person: DetailPerson?) {
ExpandableContentCard { isExpanded ->
Text(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(top = 12.dp, start = 16.dp, end = 16.dp),
text = person?.biography ?: "",
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
maxLines = if (isExpanded) Int.MAX_VALUE else 3,
overflow = TextOverflow.Ellipsis
)
if (person != null && person.biography.isNotEmpty()) {
ExpandableContentCard { isExpanded ->
Text(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(top = 12.dp, start = 16.dp, end = 16.dp),
text = person.biography,
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
maxLines = if (isExpanded) Int.MAX_VALUE else 3,
overflow = TextOverflow.Ellipsis
)
}
}
}

View File

@@ -112,22 +112,22 @@ fun SearchScreen(
SelectableTextChip(
selected = viewType.value == MediaViewType.MOVIE,
onSelected = { viewType.value = MediaViewType.MOVIE },
text = "Movies"
text = stringResource(id = R.string.nav_movies_title)
)
SelectableTextChip(
selected = viewType.value == MediaViewType.TV,
onSelected = { viewType.value = MediaViewType.TV },
text = "TV"
text = stringResource(id = R.string.nav_tv_title)
)
SelectableTextChip(
selected = viewType.value == MediaViewType.PERSON,
onSelected = { viewType.value = MediaViewType.PERSON },
text = "People"
text = stringResource(id = R.string.nav_people_title)
)
SelectableTextChip(
selected = viewType.value == MediaViewType.MIXED,
onSelected = { viewType.value = MediaViewType.MIXED },
text = "Multi"
text = stringResource(id = R.string.search_multi_title)
)
}
@@ -374,7 +374,7 @@ private fun <T: SortableSearchResult> SearchResultItemView(
id = searchResult.id,
backdropPath = backdropModel(searchResult),
posterPath = posterModel(searchResult),
title = searchResult.name,
title = searchResult.title,
additionalDetails = additionalDetails(searchResult)
)
}
@@ -453,7 +453,7 @@ private fun PeopleSearchResultView(
appNavController = appNavController,
mediaViewType = MediaViewType.PERSON,
searchResult = result,
posterModel = { TmdbUtils.getFullPersonImagePath(result.profilePath) },
posterModel = { TmdbUtils.getFullPersonImagePath(result.posterPath) },
backdropModel = { TmdbUtils.getFullBackdropPath(mostKnownFor?.backdropPath) },
additionalDetails = { additional }
)

View File

@@ -1,4 +1,4 @@
package com.owenlejeune.tvtime.ui.navigation
package com.owenlejeune.tvtime.ui.screens.tabs
import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
@@ -84,7 +84,7 @@ sealed class AccountTabNavItem(
R.string.no_favorite_movies,
MediaViewType.MOVIE,
screenContent,
AccountTabType.FAVORITE,
AccountTabType.FAVORITE,
FavoriteMovie::class,
3
)

View File

@@ -1,8 +1,16 @@
package com.owenlejeune.tvtime.ui.screens.tabs
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
@@ -13,13 +21,14 @@ import com.google.accompanist.pager.PagerState
import com.google.accompanist.pager.rememberPagerState
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.ui.components.PagingPosterGrid
import com.owenlejeune.tvtime.ui.components.ScrollableTabs
import com.owenlejeune.tvtime.ui.components.SearchView
import com.owenlejeune.tvtime.ui.components.Tabs
import com.owenlejeune.tvtime.ui.components.SelectableTextChip
import com.owenlejeune.tvtime.ui.navigation.AppNavItem
import com.owenlejeune.tvtime.ui.navigation.MediaTabNavItem
import com.owenlejeune.tvtime.ui.viewmodel.HomeScreenViewModel
import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
import com.owenlejeune.tvtime.utils.types.MediaViewType
import com.owenlejeune.tvtime.utils.types.TimeWindow
@OptIn(ExperimentalPagerApi::class)
@Composable
@@ -44,7 +53,7 @@ fun MediaTab(
val tabs = MediaTabNavItem.itemsForType(type = mediaType)
val pagerState = rememberPagerState()
Tabs(tabs = tabs, pagerState = pagerState)
ScrollableTabs(tabs = tabs, pagerState = pagerState)
MediaTabs(
tabs = tabs,
pagerState = pagerState,
@@ -73,6 +82,52 @@ fun MediaTabContent(
)
}
@Composable
fun MediaTabTrendingContent(
appNavController: NavHostController,
mediaType: MediaViewType,
mediaTabItem: MediaTabNavItem
) {
val viewModel = viewModel<MainViewModel>()
val timeWindow = remember { mutableStateOf(TimeWindow.DAY) }
val flow = remember { mutableStateOf(viewModel.produceTrendingFor(mediaType, timeWindow.value)) }
LaunchedEffect(timeWindow.value) {
flow.value = viewModel.produceTrendingFor(mediaType, timeWindow.value)
}
val mediaListItems = flow.value.collectAsLazyPagingItems()
Column {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.padding(all = 12.dp)
) {
SelectableTextChip(
selected = timeWindow.value == TimeWindow.DAY,
onSelected = { timeWindow.value = TimeWindow.DAY },
text = stringResource(id = R.string.time_window_day),
modifier = Modifier.weight(1f)
)
SelectableTextChip(
selected = timeWindow.value == TimeWindow.WEEK,
onSelected = { timeWindow.value = TimeWindow.WEEK },
text = stringResource(id = R.string.time_window_week),
modifier = Modifier.weight(1f)
)
}
PagingPosterGrid(
lazyPagingItems = mediaListItems,
onClick = { id ->
appNavController.navigate(
AppNavItem.DetailView.withArgs(mediaType, id)
)
}
)
}
}
@OptIn(ExperimentalPagerApi::class)
@Composable
fun MediaTabs(

View File

@@ -1,4 +1,4 @@
package com.owenlejeune.tvtime.ui.navigation
package com.owenlejeune.tvtime.ui.screens.tabs
import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
@@ -6,6 +6,7 @@ import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.api.tmdb.api.v3.HomePageService
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.HomePageResponse
import com.owenlejeune.tvtime.ui.screens.tabs.MediaTabContent
import com.owenlejeune.tvtime.ui.screens.tabs.MediaTabTrendingContent
import com.owenlejeune.tvtime.utils.ResourceUtils
import com.owenlejeune.tvtime.utils.types.MediaViewType
import com.owenlejeune.tvtime.utils.types.TabNavItem
@@ -27,12 +28,13 @@ sealed class MediaTabNavItem(
POPULAR,
NOW_PLAYING,
UPCOMING,
TOP_RATED
TOP_RATED,
TRENDING
}
companion object {
val MovieItems = listOf(NowPlaying, Popular, Upcoming, TopRated)
val TvItems = listOf(OnTheAir, Popular, AiringToday, TopRated)
private val MovieItems = listOf(NowPlaying, Popular, Trending, Upcoming, TopRated)
private val TvItems = listOf(OnTheAir, Popular, Trending, AiringToday, TopRated)
fun itemsForType(type: MediaViewType): List<MediaTabNavItem> {
return when (type) {
@@ -41,12 +43,6 @@ sealed class MediaTabNavItem(
else -> throw ViewableMediaTypeException(type)
}
}
private val Items = listOf(NowPlaying, Popular, TopRated, Upcoming, AiringToday, OnTheAir)
fun getByRoute(route: String?): MediaTabNavItem? {
return Items.firstOrNull { it.route == route }
}
}
object Popular: MediaTabNavItem(
@@ -85,12 +81,23 @@ sealed class MediaTabNavItem(
screen = screenContent,
type = Type.UPCOMING
)
object Trending: MediaTabNavItem(
stringRes = R.string.nav_trending_title,
route = "trending_route",
screen = trendingScreenContent,
type = Type.TRENDING
)
}
private val screenContent: MediaNavComposableFun = { appNavController, mediaViewType, mediaTabItem ->
MediaTabContent(appNavController = appNavController, mediaType = mediaViewType, mediaTabItem = mediaTabItem)
}
private val trendingScreenContent: MediaNavComposableFun = { appNavController, mediaViewType, mediaTabItem ->
MediaTabTrendingContent(appNavController = appNavController, mediaType = mediaViewType, mediaTabItem = mediaTabItem)
}
typealias MediaNavComposableFun = @Composable (NavHostController, MediaViewType, MediaTabNavItem) -> Unit
typealias MediaFetchFun = suspend (service: HomePageService, page: Int) -> Response<out HomePageResponse>

View File

@@ -1,19 +1,38 @@
package com.owenlejeune.tvtime.ui.screens.tabs
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import androidx.paging.compose.collectAsLazyPagingItems
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.R
import com.owenlejeune.tvtime.ui.components.PagingPeoplePosterGrid
import com.owenlejeune.tvtime.ui.components.PagingPosterGrid
import com.owenlejeune.tvtime.ui.components.SearchView
import com.owenlejeune.tvtime.ui.components.SelectableTextChip
import com.owenlejeune.tvtime.ui.components.Tabs
import com.owenlejeune.tvtime.ui.navigation.AppNavItem
import com.owenlejeune.tvtime.ui.viewmodel.HomeScreenViewModel
import com.owenlejeune.tvtime.ui.viewmodel.MainViewModel
import com.owenlejeune.tvtime.utils.types.MediaViewType
import com.owenlejeune.tvtime.utils.types.TimeWindow
@OptIn(ExperimentalPagerApi::class)
@Composable
fun PeopleTab(
appNavController: NavHostController,
@@ -28,17 +47,69 @@ fun PeopleTab(
mediaType = MediaViewType.PERSON
)
val mainViewModel = viewModel<MainViewModel>()
val peopleList = mainViewModel.popularPeople.collectAsLazyPagingItems()
val tabs = PeopleTabNavItem.Items
val pagerState = rememberPagerState()
Tabs(tabs = tabs, pagerState = pagerState)
PeopleTabs(
tabs = tabs,
pagerState = pagerState,
appNavController = appNavController
)
}
}
PagingPeoplePosterGrid(
lazyPagingItems = peopleList,
header = {
// Text(
// text = stringResource(R.string.popular_today_header),
// modifier = Modifier.padding(start = 8.dp)
// )
},
@Composable
fun PopularPeopleContent(
appNavController: NavController
) {
val mainViewModel = viewModel<MainViewModel>()
val peopleList = mainViewModel.popularPeople.collectAsLazyPagingItems()
PagingPeoplePosterGrid(
lazyPagingItems = peopleList,
onClick = { id ->
appNavController.navigate(
AppNavItem.DetailView.withArgs(MediaViewType.PERSON, id)
)
}
)
}
@Composable
fun TrendingPeopleContent(
appNavController: NavController
) {
val viewModel = viewModel<MainViewModel>()
val timeWindow = remember { mutableStateOf(TimeWindow.DAY) }
val flow = remember { mutableStateOf(viewModel.produceTrendingFor(MediaViewType.PERSON, timeWindow.value)) }
LaunchedEffect(timeWindow.value) {
flow.value = viewModel.produceTrendingFor(MediaViewType.PERSON, timeWindow.value)
}
val mediaListItems = flow.value.collectAsLazyPagingItems()
Column {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.padding(all = 12.dp)
) {
SelectableTextChip(
selected = timeWindow.value == TimeWindow.DAY,
onSelected = { timeWindow.value = TimeWindow.DAY },
text = stringResource(id = R.string.time_window_day),
modifier = Modifier.weight(1f)
)
SelectableTextChip(
selected = timeWindow.value == TimeWindow.WEEK,
onSelected = { timeWindow.value = TimeWindow.WEEK },
text = stringResource(id = R.string.time_window_week),
modifier = Modifier.weight(1f)
)
}
PagingPosterGrid(
lazyPagingItems = mediaListItems,
onClick = { id ->
appNavController.navigate(
AppNavItem.DetailView.withArgs(MediaViewType.PERSON, id)
@@ -46,4 +117,16 @@ fun PeopleTab(
}
)
}
}
@OptIn(ExperimentalPagerApi::class)
@Composable
fun PeopleTabs(
tabs: List<PeopleTabNavItem>,
pagerState: PagerState,
appNavController: NavHostController = rememberNavController()
) {
HorizontalPager(count = tabs.size, state = pagerState) { page ->
tabs[page].screen(appNavController)
}
}

View File

@@ -0,0 +1,34 @@
package com.owenlejeune.tvtime.ui.screens.tabs
import androidx.compose.runtime.Composable
import androidx.navigation.NavController
import com.owenlejeune.tvtime.R
import com.owenlejeune.tvtime.utils.ResourceUtils
import com.owenlejeune.tvtime.utils.types.TabNavItem
import org.koin.core.component.inject
sealed class PeopleTabNavItem(
stringRes: Int,
route: String,
val screen: @Composable (NavController) -> Unit
): TabNavItem(route) {
private val resourceUtils: ResourceUtils by inject()
override val name: String = resourceUtils.getString(stringRes)
companion object {
val Items by lazy { listOf(Popular, Trending) }
}
object Popular: PeopleTabNavItem(
stringRes = R.string.popular_today_header,
route = "people_popular_route",
screen = { PopularPeopleContent(appNavController = it) }
)
object Trending: PeopleTabNavItem(
stringRes = R.string.nav_trending_title,
route = "people_trending_route",
screen = { TrendingPeopleContent(appNavController = it) }
)
}

View File

@@ -11,7 +11,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v4.ListV4Service
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.DeleteListItemsBody
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.DeleteListItemsItem
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListUpdateBody
import com.owenlejeune.tvtime.ui.navigation.AccountTabNavItem
import com.owenlejeune.tvtime.ui.screens.tabs.AccountTabNavItem
import com.owenlejeune.tvtime.utils.SessionManager
import com.owenlejeune.tvtime.utils.types.MediaViewType
import com.owenlejeune.tvtime.utils.types.ViewableMediaTypeException

View File

@@ -19,8 +19,9 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.SearchResultMedia
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TmdbItem
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Video
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.WatchProviders
import com.owenlejeune.tvtime.ui.navigation.MediaTabNavItem
import com.owenlejeune.tvtime.ui.screens.tabs.MediaTabNavItem
import com.owenlejeune.tvtime.utils.types.MediaViewType
import com.owenlejeune.tvtime.utils.types.TimeWindow
import com.owenlejeune.tvtime.utils.types.ViewableMediaTypeException
import kotlinx.coroutines.flow.Flow
import org.koin.core.component.KoinComponent
@@ -46,22 +47,30 @@ class MainViewModel: ViewModel(), KoinComponent {
val movieAccountStates = movieService.accountStates
val movieKeywordResults = movieService.keywordResults
val popularMovies = createPagingFlow(
fetcher = { p -> movieService.getPopular(p) },
processor = { it.results }
)
val topRatedMovies = createPagingFlow(
fetcher = { p -> movieService.getTopRated(p) },
processor = { it.results }
)
val nowPlayingMovies = createPagingFlow(
fetcher = { p -> movieService.getNowPlaying(p) },
processor = { it.results }
)
val upcomingMovies = createPagingFlow(
fetcher = { p -> movieService.getUpcoming(p) },
processor = { it.results }
)
val popularMovies by lazy {
createPagingFlow(
fetcher = { p -> movieService.getPopular(p) },
processor = { it.results }
)
}
val topRatedMovies by lazy {
createPagingFlow(
fetcher = { p -> movieService.getTopRated(p) },
processor = { it.results }
)
}
val nowPlayingMovies by lazy {
createPagingFlow(
fetcher = { p -> movieService.getNowPlaying(p) },
processor = { it.results }
)
}
val upcomingMovies by lazy {
createPagingFlow(
fetcher = { p -> movieService.getUpcoming(p) },
processor = { it.results }
)
}
val detailedTv = tvService.detailTv
val tvImages = tvService.images
@@ -78,22 +87,30 @@ class MainViewModel: ViewModel(), KoinComponent {
val tvAccountStates = tvService.accountStates
val tvKeywordResults = tvService.keywordResults
val popularTv = createPagingFlow(
fetcher = { p -> tvService.getPopular(p) },
processor = { it.results }
)
val topRatedTv = createPagingFlow(
fetcher = { p -> tvService.getTopRated(p) },
processor = { it.results }
)
val airingTodayTv = createPagingFlow(
fetcher = { p -> tvService.getNowPlaying(p) },
processor = { it.results }
)
val onTheAirTv = createPagingFlow(
fetcher = { p -> tvService.getUpcoming(p) },
processor = { it.results }
)
val popularTv by lazy {
createPagingFlow(
fetcher = { p -> tvService.getPopular(p) },
processor = { it.results }
)
}
val topRatedTv by lazy{
createPagingFlow(
fetcher = { p -> tvService.getTopRated(p) },
processor = { it.results }
)
}
val airingTodayTv by lazy {
createPagingFlow(
fetcher = { p -> tvService.getNowPlaying(p) },
processor = { it.results }
)
}
val onTheAirTv by lazy {
createPagingFlow(
fetcher = { p -> tvService.getUpcoming(p) },
processor = { it.results }
)
}
val peopleMap = peopleService.peopleMap
val peopleCastMap = peopleService.castMap
@@ -101,10 +118,12 @@ class MainViewModel: ViewModel(), KoinComponent {
val peopleImagesMap = peopleService.imagesMap
val peopleExternalIdsMap = peopleService.externalIdsMap
val popularPeople = createPagingFlow(
fetcher = { p -> peopleService.getPopular(p) },
processor = { it.results }
)
val popularPeople by lazy {
createPagingFlow(
fetcher = { p -> peopleService.getPopular(p) },
processor = { it.results }
)
}
private fun <T> providesForType(type: MediaViewType, movies: () -> T, tv: () -> T): T {
return when (type) {
@@ -167,6 +186,7 @@ class MainViewModel: ViewModel(), KoinComponent {
MediaTabNavItem.Type.TOP_RATED -> topRatedMovies
MediaTabNavItem.Type.NOW_PLAYING -> nowPlayingMovies
MediaTabNavItem.Type.POPULAR -> popularMovies
else -> throw Exception("Can't produce media flow for $mediaType")
}
},
{
@@ -175,6 +195,7 @@ class MainViewModel: ViewModel(), KoinComponent {
MediaTabNavItem.Type.TOP_RATED -> topRatedTv
MediaTabNavItem.Type.NOW_PLAYING -> airingTodayTv
MediaTabNavItem.Type.POPULAR -> popularTv
else -> throw Exception("Can't produce media flow for $mediaType")
}
}
)
@@ -184,6 +205,30 @@ class MainViewModel: ViewModel(), KoinComponent {
return providesForType(mediaType, { movieKeywordResults }, { tvKeywordResults })
}
fun produceTrendingFor(mediaType: MediaViewType, timeWindow: TimeWindow): Flow<PagingData<TmdbItem>> {
return when (mediaType) {
MediaViewType.MOVIE -> {
createPagingFlow(
fetcher = { p -> movieService.getTrending(timeWindow, p) },
processor = { it.results }
)
}
MediaViewType.TV -> {
createPagingFlow(
fetcher = { p -> tvService.getTrending(timeWindow, p) },
processor = { it.results }
)
}
MediaViewType.PERSON -> {
createPagingFlow(
fetcher = { p -> peopleService.getTrending(timeWindow, p) },
processor = { it.results }
)
}
else -> throw ViewableMediaTypeException(mediaType)
}
}
suspend fun getById(id: Int, type: MediaViewType) {
when (type) {
MediaViewType.MOVIE -> movieService.getById(id)

View File

@@ -0,0 +1,10 @@
package com.owenlejeune.tvtime.utils.types
import com.google.gson.annotations.SerializedName
enum class TimeWindow {
@SerializedName("day")
DAY,
@SerializedName("week")
WEEK
}

View File

@@ -12,6 +12,7 @@
<string name="nav_upcoming_title">Upcoming</string>
<string name="nav_tv_airing_today_title">Airing Today</string>
<string name="nav_tv_on_the_air">On The Air</string>
<string name="nav_trending_title">Trending</string>
<string name="nav_account_title">Account</string>
<string name="nav_rated_movies_title">Rated Movies</string>
<string name="nav_rated_shows_title">Rated TV Shows</string>
@@ -216,6 +217,9 @@
<string name="recommended_tv_title">Recommended TV</string>
<string name="no_recommended_tv">No Recommended TV</string>
<string name="no_result_found">No more results found</string>
<string name="search_multi_title">Multi</string>
<string name="time_window_day">Day</string>
<string name="time_window_week">Week</string>
<string name="attribution_text">This product uses the TMDB API but is not endorsed or certified by TMDB.</string>
<string name="tmdb_home_page">"https://www.themoviedb.org"</string>