mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-04 01:09:05 +00:00
feat: add DetailAdditionalInfoSection and DetailProductionSection components to enhance metadata display in MetaDetailsScreen
This commit is contained in:
parent
ca2be5fdb2
commit
a4a4f3ced4
4 changed files with 253 additions and 49 deletions
|
|
@ -37,10 +37,12 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.nuvio.app.core.ui.NuvioBackButton
|
||||
import com.nuvio.app.core.ui.nuvioPlatformExtraBottomPadding
|
||||
import com.nuvio.app.features.details.components.DetailActionButtons
|
||||
import com.nuvio.app.features.details.components.DetailAdditionalInfoSection
|
||||
import com.nuvio.app.features.details.components.DetailCastSection
|
||||
import com.nuvio.app.features.details.components.DetailFloatingHeader
|
||||
import com.nuvio.app.features.details.components.DetailHero
|
||||
import com.nuvio.app.features.details.components.DetailMetaInfo
|
||||
import com.nuvio.app.features.details.components.DetailProductionSection
|
||||
import com.nuvio.app.features.details.components.DetailSeriesContent
|
||||
import com.nuvio.app.features.details.components.EpisodeWatchedActionSheet
|
||||
import com.nuvio.app.features.library.LibraryRepository
|
||||
|
|
@ -162,6 +164,17 @@ fun MetaDetailsScreen(
|
|||
}?.overview
|
||||
}
|
||||
val hasEpisodes = meta.videos.any { it.season != null || it.episode != null }
|
||||
val hasProductionSection = remember(meta) {
|
||||
meta.productionCompanies.isNotEmpty() || meta.networks.isNotEmpty()
|
||||
}
|
||||
val hasAdditionalInfoSection = remember(meta) {
|
||||
meta.status != null ||
|
||||
meta.releaseInfo != null ||
|
||||
meta.runtime != null ||
|
||||
meta.ageRating != null ||
|
||||
meta.country != null ||
|
||||
meta.language != null
|
||||
}
|
||||
val playButtonLabel = remember(movieProgress, seriesAction, meta.type, hasEpisodes) {
|
||||
when {
|
||||
(meta.type == "series" || hasEpisodes) && seriesAction != null ->
|
||||
|
|
@ -259,8 +272,16 @@ fun MetaDetailsScreen(
|
|||
|
||||
DetailMetaInfo(meta = meta)
|
||||
|
||||
if (hasEpisodes && hasProductionSection) {
|
||||
DetailProductionSection(meta = meta)
|
||||
}
|
||||
|
||||
DetailCastSection(cast = meta.cast)
|
||||
|
||||
if (!hasEpisodes && hasProductionSection) {
|
||||
DetailProductionSection(meta = meta)
|
||||
}
|
||||
|
||||
DetailSeriesContent(
|
||||
meta = meta,
|
||||
progressByVideoId = watchProgressUiState.byVideoId,
|
||||
|
|
@ -298,6 +319,14 @@ fun MetaDetailsScreen(
|
|||
},
|
||||
)
|
||||
|
||||
if (hasEpisodes && hasAdditionalInfoSection) {
|
||||
DetailAdditionalInfoSection(meta = meta)
|
||||
}
|
||||
|
||||
if (!hasEpisodes && hasAdditionalInfoSection) {
|
||||
DetailAdditionalInfoSection(meta = meta)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(32.dp + nuvioPlatformExtraBottomPadding))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
package com.nuvio.app.features.details.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.nuvio.app.features.details.MetaDetails
|
||||
|
||||
@Composable
|
||||
fun DetailAdditionalInfoSection(
|
||||
meta: MetaDetails,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val isSeriesLike = meta.type == "series" || meta.videos.any { it.season != null || it.episode != null }
|
||||
val title = if (isSeriesLike) "Show Details" else "Movie Details"
|
||||
val rows = buildList {
|
||||
meta.status?.let { add("Status" to it) }
|
||||
meta.releaseInfo?.let { add("Release Info" to it) }
|
||||
meta.runtime?.let { add("Runtime" to it.uppercase()) }
|
||||
meta.ageRating?.let { add("Certification" to it) }
|
||||
meta.country?.let { add("Origin Country" to it) }
|
||||
meta.language?.let { add("Original Language" to it.uppercase()) }
|
||||
}
|
||||
if (rows.isEmpty()) return
|
||||
|
||||
DetailSection(
|
||||
title = title,
|
||||
modifier = modifier,
|
||||
) {
|
||||
rows.forEachIndexed { index, (label, value) ->
|
||||
DetailInfoRow(
|
||||
label = label,
|
||||
value = value,
|
||||
showDivider = index < rows.lastIndex,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DetailInfoRow(
|
||||
label: String,
|
||||
value: String,
|
||||
showDivider: Boolean,
|
||||
) {
|
||||
androidx.compose.foundation.layout.Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(0.dp),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 10.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f),
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Text(
|
||||
text = value,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(start = 16.dp),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
fontWeight = FontWeight.Medium,
|
||||
textAlign = TextAlign.End,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
|
||||
if (showDivider) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(1.dp)
|
||||
.background(MaterialTheme.colorScheme.onSurface.copy(alpha = 0.10f)),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,6 @@ package com.nuvio.app.features.details.components
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -29,7 +27,6 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.unit.sp
|
||||
import com.nuvio.app.features.details.MetaDetails
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun DetailMetaInfo(
|
||||
meta: MetaDetails,
|
||||
|
|
@ -88,22 +85,6 @@ fun DetailMetaInfo(
|
|||
}
|
||||
}
|
||||
|
||||
val detailChips = buildList {
|
||||
meta.status?.let { add(it) }
|
||||
meta.country?.let { add(it) }
|
||||
meta.language?.let { add(it.uppercase()) }
|
||||
}
|
||||
if (detailChips.isNotEmpty()) {
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
detailChips.forEach { chip ->
|
||||
DetailChip(label = chip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (meta.director.isNotEmpty()) {
|
||||
MetaLabelValueRow(
|
||||
label = "Director",
|
||||
|
|
@ -118,20 +99,6 @@ fun DetailMetaInfo(
|
|||
)
|
||||
}
|
||||
|
||||
if (meta.productionCompanies.isNotEmpty()) {
|
||||
MetaLabelValueRow(
|
||||
label = "Production",
|
||||
value = meta.productionCompanies.joinToString(", ") { it.name },
|
||||
)
|
||||
}
|
||||
|
||||
if (meta.networks.isNotEmpty()) {
|
||||
MetaLabelValueRow(
|
||||
label = "Network",
|
||||
value = meta.networks.joinToString(", ") { it.name },
|
||||
)
|
||||
}
|
||||
|
||||
if (!meta.description.isNullOrBlank()) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
Column {
|
||||
|
|
@ -175,21 +142,5 @@ private fun MetaLabelValueRow(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DetailChip(label: String) {
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.6f),
|
||||
shape = RoundedCornerShape(999.dp),
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 5.dp),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val ImdbYellow = Color(0xFFF5C518)
|
||||
private val ImdbBlack = Color(0xFF000000)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
package com.nuvio.app.features.details.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil3.compose.AsyncImage
|
||||
import com.nuvio.app.features.details.MetaCompany
|
||||
import com.nuvio.app.features.details.MetaDetails
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun DetailProductionSection(
|
||||
meta: MetaDetails,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val isSeriesLike = meta.type == "series" || meta.videos.any { it.season != null || it.episode != null }
|
||||
val sourceItems = if (isSeriesLike) {
|
||||
meta.networks.ifEmpty { meta.productionCompanies }
|
||||
} else {
|
||||
meta.productionCompanies.ifEmpty { meta.networks }
|
||||
}
|
||||
if (sourceItems.isEmpty()) return
|
||||
|
||||
val displayItems = if (isSeriesLike) {
|
||||
sourceItems.take(6)
|
||||
} else {
|
||||
val logosOnly = sourceItems.filter { !it.logo.isNullOrBlank() }
|
||||
(if (logosOnly.isNotEmpty()) logosOnly else sourceItems).take(6)
|
||||
}
|
||||
if (displayItems.isEmpty()) return
|
||||
|
||||
DetailSection(
|
||||
title = if (isSeriesLike) "Network" else "Production",
|
||||
modifier = modifier,
|
||||
) {
|
||||
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
|
||||
val chipHeight = when {
|
||||
maxWidth >= 1024.dp -> 44.dp
|
||||
maxWidth >= 720.dp -> 40.dp
|
||||
else -> 36.dp
|
||||
}
|
||||
val logoWidth = when {
|
||||
maxWidth >= 1024.dp -> 72.dp
|
||||
maxWidth >= 720.dp -> 68.dp
|
||||
else -> 64.dp
|
||||
}
|
||||
val logoHeight = when {
|
||||
maxWidth >= 1024.dp -> 26.dp
|
||||
maxWidth >= 720.dp -> 24.dp
|
||||
else -> 22.dp
|
||||
}
|
||||
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
displayItems.forEach { item ->
|
||||
ProductionChip(
|
||||
item = item,
|
||||
chipHeight = chipHeight,
|
||||
logoWidth = logoWidth,
|
||||
logoHeight = logoHeight,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProductionChip(
|
||||
item: MetaCompany,
|
||||
chipHeight: androidx.compose.ui.unit.Dp,
|
||||
logoWidth: androidx.compose.ui.unit.Dp,
|
||||
logoHeight: androidx.compose.ui.unit.Dp,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(color = ProductionChipBackground)
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||
.height(chipHeight),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
if (!item.logo.isNullOrBlank()) {
|
||||
AsyncImage(
|
||||
model = item.logo,
|
||||
contentDescription = item.name,
|
||||
modifier = Modifier
|
||||
.width(logoWidth)
|
||||
.height(logoHeight),
|
||||
contentScale = ContentScale.Fit,
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = item.name,
|
||||
style = MaterialTheme.typography.labelMedium.copy(
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
),
|
||||
color = ProductionTextColor,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val ProductionChipBackground = androidx.compose.ui.graphics.Color(0xE6F5F5F5)
|
||||
private val ProductionTextColor = androidx.compose.ui.graphics.Color(0xFF333333)
|
||||
Loading…
Reference in a new issue