From 5a4bef5cb60ece195c0790204d6825b33baf3d75 Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Wed, 11 Mar 2026 21:15:12 +0530 Subject: [PATCH] refact components into reusable --- .../nuvio/app/core/ui/NuvioShelfComponents.kt | 208 ++++++++++++++++++ .../home/components/HomeCatalogSection.kt | 38 +--- .../components/HomeCatalogSectionHeader.kt | 91 -------- .../home/components/HomePosterCard.kt | 93 ++------ 4 files changed, 234 insertions(+), 196 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioShelfComponents.kt delete mode 100644 composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCatalogSectionHeader.kt diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioShelfComponents.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioShelfComponents.kt new file mode 100644 index 000000000..f80aa4c27 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioShelfComponents.kt @@ -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 NuvioShelfSection( + title: String, + entries: List, + 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 + } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCatalogSection.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCatalogSection.kt index 6dd07ef61..1b8a30617 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCatalogSection.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCatalogSection.kt @@ -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) } }, - ) - } - } } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCatalogSectionHeader.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCatalogSectionHeader.kt deleted file mode 100644 index a98ed0d87..000000000 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomeCatalogSectionHeader.kt +++ /dev/null @@ -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, - ) - } -} diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomePosterCard.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomePosterCard.kt index 531a2f14e..8498122b8 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomePosterCard.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/components/HomePosterCard.kt @@ -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 }