refact components into reusable

This commit is contained in:
tapframe 2026-03-11 21:15:12 +05:30
parent 4f09be429d
commit 5a4bef5cb6
4 changed files with 234 additions and 196 deletions

View file

@ -0,0 +1,208 @@
package com.nuvio.app.core.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
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.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
import androidx.compose.material3.Icon
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.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
enum class NuvioPosterShape {
Poster,
Square,
Landscape,
}
@Composable
fun <T> NuvioShelfSection(
title: String,
entries: List<T>,
modifier: Modifier = Modifier,
onViewAllClick: (() -> Unit)? = null,
key: ((T) -> Any)? = null,
itemContent: @Composable (T) -> Unit,
) {
Column(
modifier = modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
NuvioShelfSectionHeader(
title = title,
onViewAllClick = onViewAllClick,
)
LazyRow(
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
if (key != null) {
items(
items = entries,
key = key,
) { entry ->
itemContent(entry)
}
} else {
items(entries) { entry ->
itemContent(entry)
}
}
}
}
}
@Composable
fun NuvioPosterCard(
title: String,
imageUrl: String?,
modifier: Modifier = Modifier,
shape: NuvioPosterShape = NuvioPosterShape.Poster,
detailLine: String? = null,
onClick: (() -> Unit)? = null,
) {
Column(
modifier = modifier
.width(110.dp)
.then(if (onClick != null) Modifier.clickable(onClick = onClick) else Modifier),
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(shape.aspectRatio)
.clip(RoundedCornerShape(16.dp))
.background(MaterialTheme.colorScheme.surface),
contentAlignment = Alignment.Center,
) {
if (imageUrl != null) {
AsyncImage(
model = imageUrl,
contentDescription = title,
modifier = Modifier.matchParentSize(),
contentScale = ContentScale.Crop,
)
} else {
Text(
text = title,
modifier = Modifier.padding(horizontal = 14.dp),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
)
}
}
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (!detailLine.isNullOrBlank()) {
Text(
text = detailLine,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
} else {
Box(modifier = Modifier.height(0.dp))
}
}
}
@Composable
private fun NuvioShelfSectionHeader(
title: String,
modifier: Modifier = Modifier,
onViewAllClick: (() -> Unit)? = null,
) {
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top,
) {
Column(
modifier = Modifier.weight(1f),
) {
Text(
text = title,
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Box(
modifier = Modifier
.padding(top = 6.dp)
.width(60.dp)
.height(4.dp)
.background(
color = MaterialTheme.colorScheme.primary,
shape = RoundedCornerShape(999.dp),
),
)
}
NuvioViewAllPill(
onClick = onViewAllClick,
)
}
}
@Composable
private fun NuvioViewAllPill(
onClick: (() -> Unit)?,
) {
Row(
modifier = Modifier
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(20.dp),
)
.then(if (onClick != null) Modifier.clickable(onClick = onClick) else Modifier)
.padding(horizontal = 18.dp, vertical = 14.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = "View All",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface,
)
Icon(
imageVector = Icons.AutoMirrored.Rounded.KeyboardArrowRight,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
private val NuvioPosterShape.aspectRatio: Float
get() = when (this) {
NuvioPosterShape.Poster -> 0.675f
NuvioPosterShape.Square -> 1f
NuvioPosterShape.Landscape -> 1.77f
}

View file

@ -1,14 +1,8 @@
package com.nuvio.app.features.home.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.nuvio.app.core.ui.NuvioShelfSection
import com.nuvio.app.features.home.HomeCatalogSection
import com.nuvio.app.features.home.MetaPreview
@ -19,26 +13,16 @@ fun HomeCatalogRowSection(
onViewAllClick: (() -> Unit)? = null,
onPosterClick: ((MetaPreview) -> Unit)? = null,
) {
Column(
modifier = modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
HomeCatalogSectionHeader(
title = section.title,
onViewAllClick = onViewAllClick,
NuvioShelfSection(
title = section.title,
entries = section.items,
modifier = modifier,
onViewAllClick = onViewAllClick,
key = { item -> item.id },
) { item ->
HomePosterCard(
item = item,
onClick = onPosterClick?.let { { it(item) } },
)
LazyRow(
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
items(
items = section.items,
key = { item -> item.id },
) { item ->
HomePosterCard(
item = item,
onClick = onPosterClick?.let { { it(item) } },
)
}
}
}
}

View file

@ -1,91 +0,0 @@
package com.nuvio.app.features.home.components
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
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.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
import androidx.compose.material3.Icon
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.style.TextOverflow
import androidx.compose.ui.unit.dp
@Composable
fun HomeCatalogSectionHeader(
title: String,
modifier: Modifier = Modifier,
onViewAllClick: (() -> Unit)? = null,
) {
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.Top,
) {
Column(
modifier = Modifier.weight(1f),
) {
Text(
text = title,
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onBackground,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Box(
modifier = Modifier
.padding(top = 6.dp)
.width(60.dp)
.height(4.dp)
.background(
color = MaterialTheme.colorScheme.primary,
shape = RoundedCornerShape(999.dp),
),
)
}
ViewAllPill(
onClick = onViewAllClick,
)
}
}
@Composable
private fun ViewAllPill(
onClick: (() -> Unit)?,
) {
Row(
modifier = Modifier
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(20.dp),
)
.then(
if (onClick != null) Modifier.clickable(onClick = onClick) else Modifier
)
.padding(horizontal = 18.dp, vertical = 14.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = "View All",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface,
)
Icon(
imageVector = Icons.AutoMirrored.Rounded.KeyboardArrowRight,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}

View file

@ -1,27 +1,9 @@
package com.nuvio.app.features.home.components
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.aspectRatio
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.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import com.nuvio.app.core.ui.NuvioPosterCard
import com.nuvio.app.core.ui.NuvioPosterShape
import com.nuvio.app.features.home.MetaPreview
import com.nuvio.app.features.home.PosterShape
@ -31,64 +13,19 @@ fun HomePosterCard(
modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null,
) {
Column(
modifier = modifier
.width(110.dp)
.then(if (onClick != null) Modifier.clickable(onClick = onClick) else Modifier),
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(item.posterShape.aspectRatio)
.clip(RoundedCornerShape(16.dp))
.background(MaterialTheme.colorScheme.surface),
contentAlignment = Alignment.Center,
) {
if (item.poster != null) {
AsyncImage(
model = item.poster,
contentDescription = item.name,
modifier = Modifier.matchParentSize(),
contentScale = ContentScale.Crop,
)
} else {
Text(
text = item.name,
modifier = Modifier.padding(horizontal = 14.dp),
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
)
}
}
Text(
text = item.name,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
val detailLine = listOfNotNull(item.releaseInfo, item.imdbRating?.let { "IMDb $it" }).joinToString("")
if (detailLine.isNotBlank()) {
Text(
text = detailLine,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
} else {
Box(modifier = Modifier.height(0.dp))
}
}
NuvioPosterCard(
title = item.name,
imageUrl = item.poster,
modifier = modifier,
shape = item.posterShape.toNuvioPosterShape(),
detailLine = listOfNotNull(item.releaseInfo, item.imdbRating?.let { "IMDb $it" }).joinToString(""),
onClick = onClick,
)
}
private val PosterShape.aspectRatio: Float
get() = when (this) {
PosterShape.Poster -> 0.675f
PosterShape.Square -> 1f
PosterShape.Landscape -> 1.77f
private fun PosterShape.toNuvioPosterShape(): NuvioPosterShape =
when (this) {
PosterShape.Poster -> NuvioPosterShape.Poster
PosterShape.Square -> NuvioPosterShape.Square
PosterShape.Landscape -> NuvioPosterShape.Landscape
}