mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-21 03:00:54 -05:00
decent chunk of list details screen done
This commit is contained in:
@@ -37,10 +37,7 @@ import com.owenlejeune.tvtime.ui.navigation.BottomNavItem
|
|||||||
import com.owenlejeune.tvtime.ui.navigation.MainNavGraph
|
import com.owenlejeune.tvtime.ui.navigation.MainNavGraph
|
||||||
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
|
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
|
||||||
import com.owenlejeune.tvtime.ui.screens.SearchScreen
|
import com.owenlejeune.tvtime.ui.screens.SearchScreen
|
||||||
import com.owenlejeune.tvtime.ui.screens.main.MediaDetailView
|
import com.owenlejeune.tvtime.ui.screens.main.*
|
||||||
import com.owenlejeune.tvtime.ui.screens.main.MediaViewType
|
|
||||||
import com.owenlejeune.tvtime.ui.screens.main.PersonDetailView
|
|
||||||
import com.owenlejeune.tvtime.ui.screens.main.SettingsTab
|
|
||||||
import com.owenlejeune.tvtime.ui.theme.TVTimeTheme
|
import com.owenlejeune.tvtime.ui.theme.TVTimeTheme
|
||||||
import com.owenlejeune.tvtime.utils.KeyboardManager
|
import com.owenlejeune.tvtime.utils.KeyboardManager
|
||||||
import com.owenlejeune.tvtime.utils.SessionManager
|
import com.owenlejeune.tvtime.utils.SessionManager
|
||||||
@@ -397,9 +394,11 @@ class MainActivity : MonetCompatActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
MediaViewType.LIST -> {
|
MediaViewType.LIST -> {
|
||||||
LocalContext.current.let {
|
ListDetailView(
|
||||||
Toast.makeText(it, "It's a list!", Toast.LENGTH_SHORT).show()
|
appNavController = appNavController,
|
||||||
}
|
itemId = args.getInt(NavConstants.ID_KEY),
|
||||||
|
windowSize = windowSize
|
||||||
|
)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
MediaDetailView(
|
MediaDetailView(
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
package com.owenlejeune.tvtime.api.tmdb.api.v4
|
package com.owenlejeune.tvtime.api.tmdb.api.v4
|
||||||
|
|
||||||
|
import com.owenlejeune.tvtime.BuildConfig
|
||||||
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
||||||
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.*
|
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.*
|
||||||
|
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
|
||||||
class ListV4Service {
|
class ListV4Service: KoinComponent {
|
||||||
|
|
||||||
private val service by lazy { TmdbClient().createV4ListService() }
|
private val service by lazy { TmdbClient().createV4ListService() }
|
||||||
|
|
||||||
suspend fun getList(listId: Int, apiKey: String, page: Int = 1): Response<MediaList> {
|
private val preferences: AppPreferences by inject()
|
||||||
return service.getList(listId, apiKey, page)
|
|
||||||
|
suspend fun getList(listId: Int, page: Int = 1): Response<MediaList> {
|
||||||
|
return service.getList(listId, BuildConfig.TMDB_Api_v4Key, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createList(body: CreateListBody): Response<CreateListResponse> {
|
suspend fun createList(body: CreateListBody): Response<CreateListResponse> {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class MediaList(
|
|||||||
@SerializedName("id") val id: Int,
|
@SerializedName("id") val id: Int,
|
||||||
@SerializedName("backdrop_path") val backdropPath: String?,
|
@SerializedName("backdrop_path") val backdropPath: String?,
|
||||||
@SerializedName("total_results") val totalResults: Int,
|
@SerializedName("total_results") val totalResults: Int,
|
||||||
@SerializedName("public") val isPublic: Int,
|
@SerializedName("public") val isPublic: Boolean,
|
||||||
@SerializedName("results") val results: List<ListItem>,
|
@SerializedName("results") val results: List<ListItem>,
|
||||||
@SerializedName("iso_639_1") val language: String,
|
@SerializedName("iso_639_1") val language: String,
|
||||||
@SerializedName("total_pages") val totalPages: Int,
|
@SerializedName("total_pages") val totalPages: Int,
|
||||||
@@ -16,5 +16,6 @@ class MediaList(
|
|||||||
@SerializedName("iso_3166_1") val localeCode: String,
|
@SerializedName("iso_3166_1") val localeCode: String,
|
||||||
@SerializedName("average_rating") val averageRating: Float,
|
@SerializedName("average_rating") val averageRating: Float,
|
||||||
@SerializedName("runtime") val runtime: Int,
|
@SerializedName("runtime") val runtime: Int,
|
||||||
@SerializedName("name") val name: String
|
@SerializedName("name") val name: String,
|
||||||
|
@SerializedName("revenue") val revenue: Int
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,373 @@
|
|||||||
|
package com.owenlejeune.tvtime.ui.screens.main
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.compose.animation.rememberSplineBasedDecay
|
||||||
|
import androidx.compose.foundation.*
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.blur
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.constraintlayout.compose.ConstraintLayout
|
||||||
|
import androidx.constraintlayout.compose.Dimension
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import com.owenlejeune.tvtime.R
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v4.ListV4Service
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.ListItem
|
||||||
|
import com.owenlejeune.tvtime.api.tmdb.api.v4.model.MediaList
|
||||||
|
import com.owenlejeune.tvtime.extensions.WindowSizeClass
|
||||||
|
import com.owenlejeune.tvtime.extensions.unlessEmpty
|
||||||
|
import com.owenlejeune.tvtime.preferences.AppPreferences
|
||||||
|
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
|
||||||
|
import com.owenlejeune.tvtime.utils.TmdbUtils
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.koin.java.KoinJavaComponent
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ListDetailView(
|
||||||
|
appNavController: NavController,
|
||||||
|
itemId: Int?,
|
||||||
|
windowSize: WindowSizeClass,
|
||||||
|
preferences: AppPreferences = KoinJavaComponent.get(AppPreferences::class.java)
|
||||||
|
) {
|
||||||
|
val service = ListV4Service()
|
||||||
|
|
||||||
|
val listItem = remember { mutableStateOf<MediaList?>(null) }
|
||||||
|
itemId?.let {
|
||||||
|
if (listItem.value == null) {
|
||||||
|
fetchList(itemId, service, listItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
|
||||||
|
val topAppBarScrollState = rememberTopAppBarScrollState()
|
||||||
|
val scrollBehavior = remember(decayAnimationSpec) {
|
||||||
|
TopAppBarDefaults.pinnedScrollBehavior(topAppBarScrollState)
|
||||||
|
}
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
|
topBar = {
|
||||||
|
SmallTopAppBar(
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
colors = TopAppBarDefaults
|
||||||
|
.smallTopAppBarColors(
|
||||||
|
scrolledContainerColor = MaterialTheme.colorScheme.background,
|
||||||
|
titleContentColor = MaterialTheme.colorScheme.primary
|
||||||
|
),
|
||||||
|
title = { Text(text = listItem.value?.name ?: "") },
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(
|
||||||
|
onClick = { appNavController.popBackStack() }
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.ArrowBack,
|
||||||
|
contentDescription = stringResource(id = R.string.content_description_back_button),
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { innerPadding ->
|
||||||
|
Box(modifier = Modifier.padding(innerPadding)) {
|
||||||
|
listItem.value?.let { mediaList ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(all = 12.dp)
|
||||||
|
.verticalScroll(state = rememberScrollState()),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
ListHeader(list = mediaList)
|
||||||
|
|
||||||
|
mediaList.results.forEach { listItem ->
|
||||||
|
ListItemView(
|
||||||
|
appNavController = appNavController,
|
||||||
|
listItem = listItem
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ListHeader(list: MediaList) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
AsyncImage(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.size(50.dp),
|
||||||
|
model = TmdbUtils.getAccountGravatarUrl(list.createdBy.gravatarHash),
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.list_created_by, list.createdBy.name),
|
||||||
|
fontSize = 20.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(10.dp))
|
||||||
|
.background(color = MaterialTheme.colorScheme.surfaceVariant)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
|
modifier = Modifier.padding(all = 12.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.list_description_label),
|
||||||
|
fontSize = 22.sp
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = list.description.unlessEmpty(stringResource(id = R.string.no_description_provided)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
OverviewStatCard(top = list.results.size.toString(), bottom = stringResource(R.string.items_in_list_label))
|
||||||
|
|
||||||
|
OverviewStatCard(top = "${(list.averageRating*10).roundToInt()}%", bottom = stringResource(R.string.average_rating_label))
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
OverviewStatCard(
|
||||||
|
top = TmdbUtils.convertRuntimeToHoursAndMinutes(list.runtime),
|
||||||
|
bottom = stringResource(R.string.runtime_label)
|
||||||
|
)
|
||||||
|
|
||||||
|
OverviewStatCard(
|
||||||
|
top = TmdbUtils.formatRevenue(list.revenue),
|
||||||
|
bottom = stringResource(R.string.total_revenue_label)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
onClick = { }
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.action_edit))
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
onClick = { }
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.action_sort_by))
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
onClick = { shareListUrl(context, list.id) }
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.action_share))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RowScope.OverviewStatCard(
|
||||||
|
top: String,
|
||||||
|
bottom: String
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(10.dp))
|
||||||
|
.background(color = MaterialTheme.colorScheme.surfaceVariant)
|
||||||
|
.weight(1f)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(12.dp)
|
||||||
|
.align(Alignment.Center),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = top,
|
||||||
|
fontSize = 22.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = bottom,
|
||||||
|
fontStyle = FontStyle.Italic
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun ListItemView(
|
||||||
|
appNavController: NavController,
|
||||||
|
listItem: ListItem
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 10.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.background(color = MaterialTheme.colorScheme.background)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(
|
||||||
|
onClick = {
|
||||||
|
appNavController.navigate(
|
||||||
|
"${MainNavItem.DetailView.route}/${listItem.mediaType}/${listItem.id}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.height(112.dp)
|
||||||
|
) {
|
||||||
|
listItem.backdropPath?.let {
|
||||||
|
AsyncImage(
|
||||||
|
model = TmdbUtils.getFullBackdropPath(listItem.backdropPath),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.FillWidth,
|
||||||
|
modifier = Modifier
|
||||||
|
.blur(radius = 10.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color.Black.copy(alpha = 0.4f))
|
||||||
|
.blur(radius = 10.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ConstraintLayout(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(8.dp)
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
val (poster, content, delete) = createRefs()
|
||||||
|
|
||||||
|
AsyncImage(
|
||||||
|
modifier = Modifier
|
||||||
|
.constrainAs(poster) {
|
||||||
|
start.linkTo(parent.start)
|
||||||
|
top.linkTo(parent.top)
|
||||||
|
bottom.linkTo(parent.bottom)
|
||||||
|
height = Dimension.fillToConstraints
|
||||||
|
}
|
||||||
|
.aspectRatio(0.7f)
|
||||||
|
.clip(RoundedCornerShape(10.dp)),
|
||||||
|
model = TmdbUtils.getFullPosterPath(listItem.posterPath) ?: R.drawable.placeholder_transparent,
|
||||||
|
contentDescription = listItem.title
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.constrainAs(content) {
|
||||||
|
end.linkTo(delete.start, margin = 12.dp)
|
||||||
|
start.linkTo(poster.end, margin = 12.dp)
|
||||||
|
width = Dimension.fillToConstraints
|
||||||
|
height = Dimension.matchParent
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
val textColor = if (listItem.backdropPath != null || isSystemInDarkTheme()) {
|
||||||
|
Color.White
|
||||||
|
} else {
|
||||||
|
Color.Black
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = listItem.title,
|
||||||
|
color = textColor,
|
||||||
|
fontSize = 18.sp,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
|
||||||
|
Image(
|
||||||
|
imageVector = Icons.Filled.Delete,
|
||||||
|
contentDescription = stringResource(R.string.remove_from_list_cd),
|
||||||
|
colorFilter = ColorFilter.tint(color = Color.Red),
|
||||||
|
modifier = Modifier.constrainAs(delete) {
|
||||||
|
top.linkTo(parent.top)
|
||||||
|
bottom.linkTo(parent.bottom)
|
||||||
|
end.linkTo(parent.end, margin = 12.dp)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shareListUrl(context: Context, listId: Int) {
|
||||||
|
val shareUrl = "https://www.themoviedb.org/list/$listId"
|
||||||
|
val sendIntent = Intent().apply {
|
||||||
|
action = Intent.ACTION_SEND
|
||||||
|
putExtra(Intent.EXTRA_TEXT, shareUrl)
|
||||||
|
type = "text/plain"
|
||||||
|
}
|
||||||
|
val shareIntent = Intent.createChooser(sendIntent, null)
|
||||||
|
context.startActivity(shareIntent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fetchList(
|
||||||
|
itemId: Int,
|
||||||
|
service: ListV4Service,
|
||||||
|
listItem: MutableState<MediaList?>
|
||||||
|
) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
val response = service.getList(itemId)
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
listItem.value = response.body()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -88,7 +88,7 @@ fun MediaResultCard(
|
|||||||
}
|
}
|
||||||
.aspectRatio(0.7f)
|
.aspectRatio(0.7f)
|
||||||
.clip(RoundedCornerShape(10.dp)),
|
.clip(RoundedCornerShape(10.dp)),
|
||||||
model = posterPath ?: R.drawable.placeholder,
|
model = posterPath ?: R.drawable.placeholder_transparent,
|
||||||
contentDescription = title
|
contentDescription = title
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -105,6 +105,7 @@ fun MediaResultCard(
|
|||||||
height = Dimension.matchParent
|
height = Dimension.matchParent
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
val textColor = backdropPath?.let { Color.White } ?: if (isSystemInDarkTheme()) Color.White else Color.Black
|
val textColor = backdropPath?.let { Color.White } ?: if (isSystemInDarkTheme()) Color.White else Color.Black
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
@@ -121,6 +122,7 @@ fun MediaResultCard(
|
|||||||
overflow = TextOverflow.Ellipsis
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
}
|
}
|
||||||
|
|
||||||
rating?.let {
|
rating?.let {
|
||||||
|
|||||||
@@ -96,12 +96,12 @@ object TmdbUtils {
|
|||||||
return convertRuntimeToHoursAndMinutes(series.episodeRuntime[0])
|
return convertRuntimeToHoursAndMinutes(series.episodeRuntime[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun convertRuntimeToHoursAndMinutes(runtime: Int): String {
|
fun convertRuntimeToHoursAndMinutes(runtime: Int): String {
|
||||||
val hours = runtime / 60
|
val hours = runtime / 60
|
||||||
val minutes = runtime % 60
|
val minutes = runtime % 60
|
||||||
return if (hours > 0){
|
return if (hours > 0){
|
||||||
if (minutes > 0) {
|
if (minutes > 0) {
|
||||||
"${hours}h${minutes}"
|
"${hours}h ${minutes}m"
|
||||||
} else {
|
} else {
|
||||||
"${hours}h"
|
"${hours}h"
|
||||||
}
|
}
|
||||||
@@ -173,6 +173,10 @@ object TmdbUtils {
|
|||||||
return "${GRAVATAR_BASE}${hash}"
|
return "${GRAVATAR_BASE}${hash}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAccountGravatarUrl(gravatarHash: String): String {
|
||||||
|
return "${GRAVATAR_BASE}${gravatarHash}"
|
||||||
|
}
|
||||||
|
|
||||||
fun getAccountAvatarUrl(accountDetails: AccountDetails): String {
|
fun getAccountAvatarUrl(accountDetails: AccountDetails): String {
|
||||||
val path = accountDetails.avatar.tmdb?.avatarPath
|
val path = accountDetails.avatar.tmdb?.avatarPath
|
||||||
return "${AVATAR_BASE}${path}"
|
return "${AVATAR_BASE}${path}"
|
||||||
@@ -185,4 +189,20 @@ object TmdbUtils {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun formatRevenue(revenue: Int): String {
|
||||||
|
val decFormat = "%.1f"
|
||||||
|
val thousands = revenue.toFloat() / 1000f
|
||||||
|
if (thousands > 1000) {
|
||||||
|
val millions = thousands / 1000f
|
||||||
|
if (millions > 1000) {
|
||||||
|
val billions = millions / 1000f
|
||||||
|
val bs = decFormat.format(billions)
|
||||||
|
return "$${bs}B"
|
||||||
|
}
|
||||||
|
val ms = decFormat.format(millions)
|
||||||
|
return "$${ms}M"
|
||||||
|
}
|
||||||
|
return "$${thousands}"
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
5
app/src/main/res/drawable/placeholder_transparent.xml
Normal file
5
app/src/main/res/drawable/placeholder_transparent.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="@android:color/transparent" />
|
||||||
|
</shape>
|
||||||
@@ -183,4 +183,14 @@
|
|||||||
<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_lists">No Lists</string>
|
||||||
<string name="no_description_provided">No description provided</string>
|
<string name="no_description_provided">No description provided</string>
|
||||||
|
<string name="list_created_by">Created by %s</string>
|
||||||
|
<string name="list_description_label">About this List</string>
|
||||||
|
<string name="items_in_list_label">Items on this List</string>
|
||||||
|
<string name="average_rating_label">Average Rating</string>
|
||||||
|
<string name="runtime_label">Runtime</string>
|
||||||
|
<string name="total_revenue_label">Total Revenue</string>
|
||||||
|
<string name="action_edit">Edit</string>
|
||||||
|
<string name="action_sort_by">Sort By</string>
|
||||||
|
<string name="action_share">Share</string>
|
||||||
|
<string name="remove_from_list_cd">Remove from list</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in New Issue
Block a user