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
This commit is contained in:
damontecres 2025-11-18 17:20:28 -05:00 committed by GitHub
parent cda5b118a3
commit 1fd497d18c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 183 additions and 18 deletions

View file

@ -78,6 +78,7 @@ object AspectRatios {
const val WIDE = 16f / 9f const val WIDE = 16f / 9f
const val FOUR_THREE = 4f / 3f const val FOUR_THREE = 4f / 3f
const val TALL = 2f / 3f const val TALL = 2f / 3f
const val SQUARE = 1f
} }
@Preview( @Preview(

View file

@ -24,6 +24,7 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign 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.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
@ -469,22 +470,29 @@ fun CollectionFolderGridContent(
enter = slideInVertically() + fadeIn(), enter = slideInVertically() + fadeIn(),
exit = slideOutVertically() + fadeOut(), exit = slideOutVertically() + fadeOut(),
) { ) {
if (showTitle) { Column(
Text( verticalArrangement = Arrangement.spacedBy(8.dp),
text = title, modifier = Modifier.fillMaxWidth(),
style = MaterialTheme.typography.displayMedium, ) {
color = MaterialTheme.colorScheme.onBackground, if (showTitle) {
textAlign = TextAlign.Center, Text(
modifier = Modifier.fillMaxWidth(), text = title,
) style = MaterialTheme.typography.displayMedium,
} color = MaterialTheme.colorScheme.onBackground,
if (sortOptions.isNotEmpty()) { textAlign = TextAlign.Center,
SortByButton( maxLines = 1,
sortOptions = sortOptions, overflow = TextOverflow.Ellipsis,
current = sortAndDirection, modifier = Modifier.fillMaxWidth(),
onSortChange = onSortChange, )
modifier = Modifier, }
) if (sortOptions.isNotEmpty()) {
SortByButton(
sortOptions = sortOptions,
current = sortAndDirection,
onSortChange = onSortChange,
modifier = Modifier,
)
}
} }
} }
CardGrid( CardGrid(

View file

@ -69,6 +69,19 @@ val VideoSortOptions =
ItemSortBy.RANDOM, 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 @StringRes
fun getStringRes(sort: ItemSortBy): Int = fun getStringRes(sort: ItemSortBy): Int =
when (sort) { when (sort) {

View file

@ -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,
)
}

View file

@ -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,
)
}

View file

@ -6,9 +6,11 @@ import androidx.tv.material3.Text
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.components.ItemGrid import com.github.damontecres.wholphin.ui.components.ItemGrid
import com.github.damontecres.wholphin.ui.components.LicenseInfo 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.CollectionFolderGeneric
import com.github.damontecres.wholphin.ui.detail.CollectionFolderLiveTv import com.github.damontecres.wholphin.ui.detail.CollectionFolderLiveTv
import com.github.damontecres.wholphin.ui.detail.CollectionFolderMovie 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.CollectionFolderTv
import com.github.damontecres.wholphin.ui.detail.DebugPage import com.github.damontecres.wholphin.ui.detail.DebugPage
import com.github.damontecres.wholphin.ui.detail.FavoritesPage import com.github.damontecres.wholphin.ui.detail.FavoritesPage
@ -97,7 +99,7 @@ fun DestinationContent(
) )
BaseItemKind.BOX_SET -> BaseItemKind.BOX_SET ->
CollectionFolderGeneric( CollectionFolderBoxSet(
preferences, preferences,
destination.itemId, destination.itemId,
destination.item, destination.item,
@ -122,7 +124,7 @@ fun DestinationContent(
) )
CollectionType.BOXSETS -> CollectionType.BOXSETS ->
CollectionFolderGeneric( CollectionFolderBoxSet(
preferences, preferences,
destination.itemId, destination.itemId,
destination.item, destination.item,
@ -170,6 +172,15 @@ fun DestinationContent(
modifier, modifier,
) )
CollectionType.PLAYLISTS ->
CollectionFolderPlaylist(
preferences,
destination.itemId,
destination.item,
true,
modifier,
)
else -> else ->
CollectionFolderGeneric( CollectionFolderGeneric(
preferences, preferences,