mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-17 01:00:55 -05:00
display list of seasons/episodes on tv series details page
This commit is contained in:
@@ -55,4 +55,7 @@ interface TvApi {
|
|||||||
@Query("session_id") sessionId: String
|
@Query("session_id") sessionId: String
|
||||||
): Response<StatusResponse>
|
): Response<StatusResponse>
|
||||||
|
|
||||||
|
@GET("tv/{id}/season/{season}")
|
||||||
|
suspend fun getSeason(@Path("id") seriesId: Int, @Path("season") seasonNumber: Int): Response<Season>
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ImageCollection
|
|||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KeywordsResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.KeywordsResponse
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatingBody
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.RatingBody
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ReviewResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.ReviewResponse
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.Season
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.StatusResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.StatusResponse
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvContentRatings
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.TvContentRatings
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.VideoResponse
|
import com.owenlejeune.tvtime.api.tmdb.api.v3.model.VideoResponse
|
||||||
@@ -77,4 +78,8 @@ class TvService: KoinComponent, DetailService, HomePageService {
|
|||||||
return service.getKeywords(id)
|
return service.getKeywords(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getSeason(seriesId: Int, seasonId: Int): Response<Season> {
|
||||||
|
return service.getSeason(seriesId, seasonId)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class Episode(
|
||||||
|
@SerializedName("name")
|
||||||
|
val name: String,
|
||||||
|
@SerializedName("overview")
|
||||||
|
val overview: String,
|
||||||
|
@SerializedName("episode_number")
|
||||||
|
val episodeNumber: Int,
|
||||||
|
@SerializedName("season_number")
|
||||||
|
val seasonNumber: Int,
|
||||||
|
@SerializedName("still_path")
|
||||||
|
val stillPath: String?,
|
||||||
|
@SerializedName("air_date")
|
||||||
|
val airDate: String?
|
||||||
|
)
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.owenlejeune.tvtime.api.tmdb.api.v3.model
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class Season(
|
||||||
|
@SerializedName("name")
|
||||||
|
val name: String,
|
||||||
|
@SerializedName("episodes")
|
||||||
|
val episodes: List<Episode>
|
||||||
|
)
|
||||||
@@ -1,40 +1,47 @@
|
|||||||
package com.owenlejeune.tvtime.ui.screens.main
|
package com.owenlejeune.tvtime.ui.screens.main
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.media.MediaActionSound
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.animation.*
|
import androidx.compose.animation.*
|
||||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
import androidx.compose.animation.core.LinearEasing
|
||||||
|
import androidx.compose.animation.core.RepeatMode
|
||||||
|
import androidx.compose.animation.core.infiniteRepeatable
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.Delete
|
|
||||||
import androidx.compose.material.icons.filled.Movie
|
import androidx.compose.material.icons.filled.Movie
|
||||||
import androidx.compose.material.icons.filled.Send
|
import androidx.compose.material.icons.filled.Send
|
||||||
|
import androidx.compose.material.icons.outlined.ExpandLess
|
||||||
|
import androidx.compose.material.icons.outlined.ExpandMore
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.DpSize
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.google.accompanist.flowlayout.FlowRow
|
import coil.compose.AsyncImage
|
||||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||||
|
import com.google.accompanist.pager.HorizontalPager
|
||||||
import com.google.accompanist.pager.PagerState
|
import com.google.accompanist.pager.PagerState
|
||||||
import com.google.accompanist.pager.rememberPagerState
|
import com.google.accompanist.pager.rememberPagerState
|
||||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
@@ -49,6 +56,8 @@ 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.*
|
||||||
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
|
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
|
||||||
|
import com.owenlejeune.tvtime.ui.navigation.TabNavItem
|
||||||
|
import com.owenlejeune.tvtime.ui.screens.main.tabs.top.Tabs
|
||||||
import com.owenlejeune.tvtime.ui.theme.FavoriteSelected
|
import com.owenlejeune.tvtime.ui.theme.FavoriteSelected
|
||||||
import com.owenlejeune.tvtime.ui.theme.RatingSelected
|
import com.owenlejeune.tvtime.ui.theme.RatingSelected
|
||||||
import com.owenlejeune.tvtime.ui.theme.WatchlistSelected
|
import com.owenlejeune.tvtime.ui.theme.WatchlistSelected
|
||||||
@@ -57,7 +66,6 @@ import com.owenlejeune.tvtime.utils.SessionManager
|
|||||||
import com.owenlejeune.tvtime.utils.TmdbUtils
|
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import org.koin.java.KoinJavaComponent
|
|
||||||
import org.koin.java.KoinJavaComponent.get
|
import org.koin.java.KoinJavaComponent.get
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
@@ -194,7 +202,7 @@ private fun MediaViewContent(
|
|||||||
)
|
)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
// modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
if (type == MediaViewType.MOVIE) {
|
if (type == MediaViewType.MOVIE) {
|
||||||
@@ -205,18 +213,55 @@ private fun MediaViewContent(
|
|||||||
|
|
||||||
ActionsView(itemId = itemId, type = type, service = service)
|
ActionsView(itemId = itemId, type = type, service = service)
|
||||||
|
|
||||||
OverviewCard(itemId = itemId, mediaItem = mediaItem, service = service)
|
if (type == MediaViewType.MOVIE) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
MainContent(
|
||||||
|
itemId = itemId,
|
||||||
|
mediaItem = mediaItem,
|
||||||
|
type = type,
|
||||||
|
service = service,
|
||||||
|
appNavController = appNavController,
|
||||||
|
windowSize = windowSize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val tabState = rememberPagerState()
|
||||||
|
val tabs = listOf(DetailsTab, SeasonsTab)
|
||||||
|
TvSeriesTabs(pagerState = tabState, tabs = tabs)
|
||||||
|
HorizontalPager(
|
||||||
|
count = tabs.size,
|
||||||
|
state = tabState,
|
||||||
|
userScrollEnabled = false
|
||||||
|
) { page ->
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
when (tabs[page]) {
|
||||||
|
is DetailsTab -> {
|
||||||
|
MainContent(
|
||||||
|
itemId = itemId,
|
||||||
|
mediaItem = mediaItem,
|
||||||
|
type = type,
|
||||||
|
service = service,
|
||||||
|
appNavController = appNavController,
|
||||||
|
windowSize = windowSize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
CastCard(itemId = itemId, service = service, appNavController = appNavController)
|
is SeasonsTab -> {
|
||||||
|
SeasonsTab(
|
||||||
SimilarContentCard(itemId = itemId, service = service, mediaType = type, appNavController = appNavController)
|
itemId = itemId,
|
||||||
|
mediaItem = mediaItem,
|
||||||
VideosCard(itemId = itemId, service = service)
|
service = service as TvService
|
||||||
|
)
|
||||||
AdditionalDetailsCard(itemId = itemId, mediaItem = mediaItem, service = service, type = type)
|
}
|
||||||
|
}
|
||||||
if (windowSize != WindowSizeClass.Expanded) {
|
}
|
||||||
ReviewsCard(itemId = itemId, service = service)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,6 +283,173 @@ private fun MediaViewContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalPagerApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun TvSeriesTabs(
|
||||||
|
pagerState: PagerState,
|
||||||
|
tabs: List<TabNavItem>
|
||||||
|
) {
|
||||||
|
Tabs(
|
||||||
|
modifier = Modifier.offset(y = (-16).dp),
|
||||||
|
pagerState = pagerState,
|
||||||
|
tabs = tabs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SeasonsTab(
|
||||||
|
itemId: Int?,
|
||||||
|
mediaItem: MutableState<DetailedItem?>,
|
||||||
|
service: TvService
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
mediaItem.value?.let { tv ->
|
||||||
|
val series = tv as DetailedTv
|
||||||
|
|
||||||
|
val seasons = remember { mutableStateMapOf<Int, Season>() }
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
itemId?.let {
|
||||||
|
for (i in 0..series.numberOfSeasons) {
|
||||||
|
scope.launch {
|
||||||
|
val season = service.getSeason(it, i)
|
||||||
|
if (season.isSuccessful) {
|
||||||
|
seasons[i] = season.body()!!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
seasons.toSortedMap().values.forEach { season ->
|
||||||
|
SeasonSection(season = season)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SeasonSection(season: Season) {
|
||||||
|
var isExpanded by remember { mutableStateOf(false) }
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(10.dp))
|
||||||
|
.background(color = MaterialTheme.colorScheme.surfaceVariant)
|
||||||
|
.clickable {
|
||||||
|
isExpanded = !isExpanded
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(horizontal = 24.dp, vertical = 24.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = season.name,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
var currentRotation by remember { mutableStateOf(0f) }
|
||||||
|
val rotation = remember { androidx.compose.animation.core.Animatable(currentRotation) }
|
||||||
|
LaunchedEffect(isExpanded) {
|
||||||
|
rotation.animateTo(
|
||||||
|
targetValue = if (isExpanded) 180f else 0f,
|
||||||
|
animationSpec = tween(200, easing = LinearEasing)
|
||||||
|
) {
|
||||||
|
currentRotation = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.ExpandMore,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(32.dp)
|
||||||
|
.rotate(currentRotation),
|
||||||
|
tint = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isExpanded,
|
||||||
|
enter = expandVertically(),
|
||||||
|
exit = shrinkVertically()
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
season.episodes.forEachIndexed { index, episode ->
|
||||||
|
EpisodeItem(episode = episode)
|
||||||
|
if (index != season.episodes.size - 1) {
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun EpisodeItem(episode: Episode) {
|
||||||
|
val height = 170.dp
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
modifier = Modifier.height(height)
|
||||||
|
) {
|
||||||
|
AsyncImage(
|
||||||
|
model = TmdbUtils.getFullEpisodeStillPath(episode),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(width = 100.dp, height = height)
|
||||||
|
.clip(RoundedCornerShape(10.dp)),
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "S${episode.seasonNumber}E${episode.episodeNumber} • ${episode.name}",
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
TmdbUtils.convertEpisodeDate(episode.airDate)?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
|
fontSize = 12.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = episode.overview,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun MainContent(
|
||||||
|
itemId: Int?,
|
||||||
|
mediaItem: MutableState<DetailedItem?>,
|
||||||
|
type: MediaViewType,
|
||||||
|
service: DetailService,
|
||||||
|
appNavController: NavController,
|
||||||
|
windowSize: WindowSizeClass
|
||||||
|
) {
|
||||||
|
OverviewCard(itemId = itemId, mediaItem = mediaItem, service = service)
|
||||||
|
|
||||||
|
CastCard(itemId = itemId, service = service, appNavController = appNavController)
|
||||||
|
|
||||||
|
SimilarContentCard(itemId = itemId, service = service, mediaType = type, appNavController = appNavController)
|
||||||
|
|
||||||
|
VideosCard(itemId = itemId, service = service, modifier = Modifier.fillMaxWidth())
|
||||||
|
|
||||||
|
AdditionalDetailsCard(itemId = itemId, mediaItem = mediaItem, service = service, type = type)
|
||||||
|
|
||||||
|
if (windowSize != WindowSizeClass.Expanded) {
|
||||||
|
ReviewsCard(itemId = itemId, service = service)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MiscTvDetails(mediaItem: MutableState<DetailedItem?>, service: TvService) {
|
private fun MiscTvDetails(mediaItem: MutableState<DetailedItem?>, service: TvService) {
|
||||||
mediaItem.value?.let { tv ->
|
mediaItem.value?.let { tv ->
|
||||||
@@ -249,7 +461,8 @@ private fun MiscTvDetails(mediaItem: MutableState<DetailedItem?>, service: TvSer
|
|||||||
MiscDetails(
|
MiscDetails(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight(),
|
.wrapContentHeight()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
year = TmdbUtils.getSeriesRun(series),
|
year = TmdbUtils.getSeriesRun(series),
|
||||||
runtime = TmdbUtils.convertRuntimeToHoursMinutes(series),
|
runtime = TmdbUtils.convertRuntimeToHoursMinutes(series),
|
||||||
genres = series.genres,
|
genres = series.genres,
|
||||||
@@ -269,7 +482,8 @@ private fun MiscMovieDetails(mediaItem: MutableState<DetailedItem?>, service: Mo
|
|||||||
MiscDetails(
|
MiscDetails(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight(),
|
.wrapContentHeight()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
year = TmdbUtils.getMovieReleaseYear(movie),
|
year = TmdbUtils.getMovieReleaseYear(movie),
|
||||||
runtime = TmdbUtils.convertRuntimeToHoursMinutes(movie),
|
runtime = TmdbUtils.convertRuntimeToHoursMinutes(movie),
|
||||||
genres = movie.genres,
|
genres = movie.genres,
|
||||||
@@ -328,7 +542,8 @@ private fun ActionsView(
|
|||||||
val session = SessionManager.currentSession.value
|
val session = SessionManager.currentSession.value
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.wrapContentSize(),
|
.wrapContentSize()
|
||||||
|
.padding(horizontal = 16.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
RateButton(
|
RateButton(
|
||||||
@@ -985,7 +1200,11 @@ fun SimilarContentCard(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VideosCard(itemId: Int?, service: DetailService, modifier: Modifier = Modifier) {
|
fun VideosCard(
|
||||||
|
itemId: Int?,
|
||||||
|
service: DetailService,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
val videoResponse = remember { mutableStateOf<VideoResponse?>(null) }
|
val videoResponse = remember { mutableStateOf<VideoResponse?>(null) }
|
||||||
itemId?.let {
|
itemId?.let {
|
||||||
if (videoResponse.value == null) {
|
if (videoResponse.value == null) {
|
||||||
@@ -1374,3 +1593,12 @@ private fun addToFavorite(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object DetailsTab: TabNavItem("details_route") {
|
||||||
|
override val name: String
|
||||||
|
get() = "Details"
|
||||||
|
}
|
||||||
|
object SeasonsTab: TabNavItem("seasons_route") {
|
||||||
|
override val name: String
|
||||||
|
get() = "Seasons"
|
||||||
|
}
|
||||||
@@ -75,9 +75,9 @@ sealed class OnboardingPage(
|
|||||||
.padding(all = 24.dp)
|
.padding(all = 24.dp)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.tmdb_logo),
|
painter = painterResource(id = R.drawable.tmdb_logo_long),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(32.dp),
|
modifier = Modifier.height(height = 16.dp),
|
||||||
tint = Color.Unspecified
|
tint = Color.Unspecified
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ object TmdbUtils {
|
|||||||
private const val PERSON_BASE = "https://www.themoviedb.org/t/p/w600_and_h900_bestv2"
|
private const val PERSON_BASE = "https://www.themoviedb.org/t/p/w600_and_h900_bestv2"
|
||||||
private const val GRAVATAR_BASE = "https://www.gravatar.com/avatar/"
|
private const val GRAVATAR_BASE = "https://www.gravatar.com/avatar/"
|
||||||
private const val AVATAR_BASE = "https://www.themoviedb.org/t/p/w150_and_h150_face"
|
private const val AVATAR_BASE = "https://www.themoviedb.org/t/p/w150_and_h150_face"
|
||||||
|
private const val STILL_BASE = "https://www.themoviedb.org/t/p/w454_and_h254_bestv2/"
|
||||||
|
|
||||||
private const val DEF_REGION = "US"
|
private const val DEF_REGION = "US"
|
||||||
|
|
||||||
@@ -63,6 +64,16 @@ object TmdbUtils {
|
|||||||
return getFullAvatarPath(author.avatarPath)
|
return getFullAvatarPath(author.avatarPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getFullEpisodeStillPath(path: String?): String? {
|
||||||
|
return path?.let {
|
||||||
|
"${STILL_BASE}${path}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getFullEpisodeStillPath(episode: Episode): String? {
|
||||||
|
return getFullEpisodeStillPath(episode.stillPath)
|
||||||
|
}
|
||||||
|
|
||||||
fun getMovieReleaseYear(movie: DetailedMovie): String {
|
fun getMovieReleaseYear(movie: DetailedMovie): String {
|
||||||
return movie.releaseDate.split("-")[0]
|
return movie.releaseDate.split("-")[0]
|
||||||
}
|
}
|
||||||
@@ -208,4 +219,15 @@ object TmdbUtils {
|
|||||||
return "$${thousands}"
|
return "$${thousands}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun convertEpisodeDate(inDate: String?): String? {
|
||||||
|
if (inDate == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val origFormat = SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault())
|
||||||
|
val outFormat = SimpleDateFormat("MMMM dd, yyyy", java.util.Locale.getDefault())
|
||||||
|
|
||||||
|
return origFormat.parse(inDate)?.let { outFormat.format(it) }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
22
app/src/main/res/drawable/tmdb_logo_long.xml
Normal file
22
app/src/main/res/drawable/tmdb_logo_long.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="489.04dp"
|
||||||
|
android:height="35.4dp"
|
||||||
|
android:viewportWidth="489.04"
|
||||||
|
android:viewportHeight="35.4">
|
||||||
|
<path
|
||||||
|
android:pathData="M293.5,0h8.9l8.75,23.2h0.1L320.15,0h8.35L313.9,35.4h-6.25ZM340.1,0h7.8L347.9,35.4h-7.8ZM362.3,0h24.05L386.35,7.2L370.1,7.2v6.6h15.35L385.45,21L370.1,21v7.2h17.15v7.2L362.3,35.4ZM417.3,0L429,0a33.54,33.54 0,0 1,8.07 1A18.55,18.55 0,0 1,443.75 4a15.1,15.1 0,0 1,4.52 5.53A18.5,18.5 0,0 1,450 17.8a16.91,16.91 0,0 1,-1.63 7.58,16.37 16.37,0 0,1 -4.37,5.5 19.52,19.52 0,0 1,-6.35 3.37A24.59,24.59 0,0 1,430 35.4L417.29,35.4ZM425.11,28.2h4a21.57,21.57 0,0 0,5 -0.55,10.87 10.87,0 0,0 4,-1.83 8.69,8.69 0,0 0,2.67 -3.34,11.92 11.92,0 0,0 1,-5.08 9.87,9.87 0,0 0,-1 -4.52,9 9,0 0,0 -2.62,-3.18 11.68,11.68 0,0 0,-3.88 -1.88,17.43 17.43,0 0,0 -4.67,-0.62h-4.6ZM461.24,0h13.2a34.42,34.42 0,0 1,4.63 0.32,12.9 12.9,0 0,1 4.17,1.3 7.88,7.88 0,0 1,3 2.73A8.34,8.34 0,0 1,487.39 9a7.42,7.42 0,0 1,-1.67 5,9.28 9.28,0 0,1 -4.43,2.82v0.1a10,10 0,0 1,3.18 1,8.38 8.38,0 0,1 2.45,1.85 7.79,7.79 0,0 1,1.57 2.62,9.16 9.16,0 0,1 0.55,3.2 8.52,8.52 0,0 1,-1.2 4.68,9.42 9.42,0 0,1 -3.1,3 13.38,13.38 0,0 1,-4.27 1.65,23.11 23.11,0 0,1 -4.73,0.5h-14.5ZM469,14.15h5.65a8.16,8.16 0,0 0,1.78 -0.2A4.78,4.78 0,0 0,478 13.3a3.34,3.34 0,0 0,1.13 -1.2,3.63 3.63,0 0,0 0.42,-1.8 3.22,3.22 0,0 0,-0.47 -1.82,3.33 3.33,0 0,0 -1.23,-1.13 5.77,5.77 0,0 0,-1.7 -0.58,10.79 10.79,0 0,0 -1.85,-0.17L469,6.6ZM469,28.8h7a8.91,8.91 0,0 0,1.83 -0.2,4.78 4.78,0 0,0 1.67,-0.7 4,4 0,0 0,1.23 -1.3,3.71 3.71,0 0,0 0.47,-2 3.13,3.13 0,0 0,-0.62 -2A4,4 0,0 0,479 21.45,7.83 7.83,0 0,0 477,20.9a15.12,15.12 0,0 0,-2.05 -0.15L469,20.75ZM204,35.33L271,35.33a17.66,17.66 0,0 0,17.66 -17.66h0A17.67,17.67 0,0 0,271 0L204.06,0A17.67,17.67 0,0 0,186.4 17.67h0A17.66,17.66 0,0 0,204.06 35.33ZM10.1,6.9L0,6.9L0,0L28,0L28,6.9L17.9,6.9L17.9,35.4L10.1,35.4ZM39,0h7.8L46.8,13.2L61.9,13.2L61.9,0h7.8L69.7,35.4L61.9,35.4L61.9,20.1L46.75,20.1L46.75,35.4L39,35.4ZM80.2,0h24L104.2,7.2L88,7.2v6.6h15.35L103.35,21L88,21v7.2h17.15v7.2h-25ZM135.2,0L147,0l8.15,23.1h0.1L163.45,0L175.2,0L175.2,35.4h-7.8L167.4,8.25h-0.1L158,35.4h-5.95l-9,-27.15L143,8.25L143,35.4h-7.8Z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:startX="0"
|
||||||
|
android:startY="17.7"
|
||||||
|
android:endX="489.04"
|
||||||
|
android:endY="17.7"
|
||||||
|
android:type="linear">
|
||||||
|
<item android:offset="0" android:color="#FF90CEA1"/>
|
||||||
|
<item android:offset="0.56" android:color="#FF3CBEC9"/>
|
||||||
|
<item android:offset="1" android:color="#FF00B3E5"/>
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
</vector>
|
||||||
Reference in New Issue
Block a user