From 1fd497d18cb66451e1e3bc7beb773c8706892207 Mon Sep 17 00:00:00 2001 From: damontecres <154766448+damontecres@users.noreply.github.com> Date: Tue, 18 Nov 2025 17:20:28 -0500 Subject: [PATCH] Add playlist & collection specific grid layout UIs (#246) Adds grid layouts specifically for playlists and collections/boxsets instead of falling back to the generic grid. In #238, the generic grid was updated to show 4 columns of 16x9 cards. Now, collections will use 2x3 (poster) cards (like Movies) and playlists will use square cards. This also means collections & playlists have more relevant sort options. Fixes #243 Closes #226 --- .../damontecres/wholphin/ui/UiConstants.kt | 1 + .../ui/components/CollectionFolderGrid.kt | 40 ++++++----- .../wholphin/ui/data/SortAndDirection.kt | 13 ++++ .../ui/detail/CollectionFolderBoxSet.kt | 66 +++++++++++++++++++ .../ui/detail/CollectionFolderPlaylist.kt | 66 +++++++++++++++++++ .../wholphin/ui/nav/DestinationContent.kt | 15 ++++- 6 files changed, 183 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderBoxSet.kt create mode 100644 app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderPlaylist.kt diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/UiConstants.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/UiConstants.kt index cc4791e2..c15fe0b4 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/UiConstants.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/UiConstants.kt @@ -78,6 +78,7 @@ object AspectRatios { const val WIDE = 16f / 9f const val FOUR_THREE = 4f / 3f const val TALL = 2f / 3f + const val SQUARE = 1f } @Preview( diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt index 0373a188..85c2ca35 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/CollectionFolderGrid.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel @@ -469,22 +470,29 @@ fun CollectionFolderGridContent( enter = slideInVertically() + fadeIn(), exit = slideOutVertically() + fadeOut(), ) { - if (showTitle) { - Text( - text = title, - style = MaterialTheme.typography.displayMedium, - color = MaterialTheme.colorScheme.onBackground, - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth(), - ) - } - if (sortOptions.isNotEmpty()) { - SortByButton( - sortOptions = sortOptions, - current = sortAndDirection, - onSortChange = onSortChange, - modifier = Modifier, - ) + Column( + verticalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.fillMaxWidth(), + ) { + if (showTitle) { + Text( + text = title, + style = MaterialTheme.typography.displayMedium, + color = MaterialTheme.colorScheme.onBackground, + textAlign = TextAlign.Center, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.fillMaxWidth(), + ) + } + if (sortOptions.isNotEmpty()) { + SortByButton( + sortOptions = sortOptions, + current = sortAndDirection, + onSortChange = onSortChange, + modifier = Modifier, + ) + } } } CardGrid( diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/data/SortAndDirection.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/data/SortAndDirection.kt index bae98972..3acbec62 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/data/SortAndDirection.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/data/SortAndDirection.kt @@ -69,6 +69,19 @@ val VideoSortOptions = ItemSortBy.RANDOM, ) +val PlaylistSortOptions = + listOf( + ItemSortBy.SORT_NAME, + ItemSortBy.PREMIERE_DATE, + ItemSortBy.DATE_CREATED, + ItemSortBy.DATE_PLAYED, + ItemSortBy.COMMUNITY_RATING, + ItemSortBy.CRITIC_RATING, + ItemSortBy.RUNTIME, + ItemSortBy.PLAY_COUNT, + ItemSortBy.RANDOM, + ) + @StringRes fun getStringRes(sort: ItemSortBy): Int = when (sort) { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderBoxSet.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderBoxSet.kt new file mode 100644 index 00000000..8aac669c --- /dev/null +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderBoxSet.kt @@ -0,0 +1,66 @@ +package com.github.damontecres.wholphin.ui.detail + +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import com.github.damontecres.wholphin.data.model.BaseItem +import com.github.damontecres.wholphin.data.model.GetItemsFilter +import com.github.damontecres.wholphin.preferences.UserPreferences +import com.github.damontecres.wholphin.ui.AspectRatios +import com.github.damontecres.wholphin.ui.cards.GridCard +import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid +import com.github.damontecres.wholphin.ui.components.CollectionFolderGridParameters +import com.github.damontecres.wholphin.ui.data.MovieSortOptions +import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel +import java.util.UUID + +@Composable +fun CollectionFolderBoxSet( + preferences: UserPreferences, + itemId: UUID, + item: BaseItem?, + recursive: Boolean, + modifier: Modifier = Modifier, + filter: GetItemsFilter = GetItemsFilter(), + preferencesViewModel: PreferencesViewModel = hiltViewModel(), +) { + var showHeader by remember { mutableStateOf(true) } + val params = + CollectionFolderGridParameters( + columns = 6, + spacing = 16.dp, + cardContent = { item, onClick, onLongClick, mod -> + GridCard( + item = item, + onClick = onClick, + onLongClick = onLongClick, + imageContentScale = ContentScale.FillBounds, + imageAspectRatio = AspectRatios.TALL, + modifier = mod, + ) + }, + ) + CollectionFolderGrid( + preferences = preferences, + onClickItem = { _, item -> preferencesViewModel.navigationManager.navigateTo(item.destination()) }, + itemId = itemId, + initialFilter = filter, + showTitle = showHeader, + recursive = recursive, + sortOptions = MovieSortOptions, + modifier = + modifier + .padding(start = 16.dp), + positionCallback = { columns, position -> + showHeader = position < columns + }, + params = params, + ) +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderPlaylist.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderPlaylist.kt new file mode 100644 index 00000000..2ae33bcd --- /dev/null +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/CollectionFolderPlaylist.kt @@ -0,0 +1,66 @@ +package com.github.damontecres.wholphin.ui.detail + +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import com.github.damontecres.wholphin.data.model.BaseItem +import com.github.damontecres.wholphin.data.model.GetItemsFilter +import com.github.damontecres.wholphin.preferences.UserPreferences +import com.github.damontecres.wholphin.ui.AspectRatios +import com.github.damontecres.wholphin.ui.cards.GridCard +import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid +import com.github.damontecres.wholphin.ui.components.CollectionFolderGridParameters +import com.github.damontecres.wholphin.ui.data.PlaylistSortOptions +import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel +import java.util.UUID + +@Composable +fun CollectionFolderPlaylist( + preferences: UserPreferences, + itemId: UUID, + item: BaseItem?, + recursive: Boolean, + modifier: Modifier = Modifier, + filter: GetItemsFilter = GetItemsFilter(), + preferencesViewModel: PreferencesViewModel = hiltViewModel(), +) { + var showHeader by remember { mutableStateOf(true) } + val params = + CollectionFolderGridParameters( + columns = 6, + spacing = 16.dp, + cardContent = { item, onClick, onLongClick, mod -> + GridCard( + item = item, + onClick = onClick, + onLongClick = onLongClick, + imageContentScale = ContentScale.FillBounds, + imageAspectRatio = AspectRatios.SQUARE, + modifier = mod, + ) + }, + ) + CollectionFolderGrid( + preferences = preferences, + onClickItem = { _, item -> preferencesViewModel.navigationManager.navigateTo(item.destination()) }, + itemId = itemId, + initialFilter = filter, + showTitle = showHeader, + recursive = recursive, + sortOptions = PlaylistSortOptions, + modifier = + modifier + .padding(start = 16.dp), + positionCallback = { columns, position -> + showHeader = position < columns + }, + params = params, + ) +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt index ebd8ec2d..786239ef 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt @@ -6,9 +6,11 @@ import androidx.tv.material3.Text import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.ui.components.ItemGrid import com.github.damontecres.wholphin.ui.components.LicenseInfo +import com.github.damontecres.wholphin.ui.detail.CollectionFolderBoxSet import com.github.damontecres.wholphin.ui.detail.CollectionFolderGeneric import com.github.damontecres.wholphin.ui.detail.CollectionFolderLiveTv import com.github.damontecres.wholphin.ui.detail.CollectionFolderMovie +import com.github.damontecres.wholphin.ui.detail.CollectionFolderPlaylist import com.github.damontecres.wholphin.ui.detail.CollectionFolderTv import com.github.damontecres.wholphin.ui.detail.DebugPage import com.github.damontecres.wholphin.ui.detail.FavoritesPage @@ -97,7 +99,7 @@ fun DestinationContent( ) BaseItemKind.BOX_SET -> - CollectionFolderGeneric( + CollectionFolderBoxSet( preferences, destination.itemId, destination.item, @@ -122,7 +124,7 @@ fun DestinationContent( ) CollectionType.BOXSETS -> - CollectionFolderGeneric( + CollectionFolderBoxSet( preferences, destination.itemId, destination.item, @@ -170,6 +172,15 @@ fun DestinationContent( modifier, ) + CollectionType.PLAYLISTS -> + CollectionFolderPlaylist( + preferences, + destination.itemId, + destination.item, + true, + modifier, + ) + else -> CollectionFolderGeneric( preferences,