Variety of UI changes for grids, home page order, & tv guide (#272)

## Details
This PR ended up being larger than I meant. It contains some UI changes
and code organization.

## TV & DVR
- Uses the "Recordings" library position to order "Recently added in
Recordings" instead of Live TV position
- The times at the top of the TV guide are more distinguishable from
each other
- Removes the duplicate title on the Live TV->Recordings tab

## Other libraries
- Bare-bone support for "Music Video" libraries. Full support for "Music
Video" is work-in-progress in #267, but the libraries should be
browseable now.
- Bare-bone support for "Other" (aka Mixed Movies & TV Shows) libraries.
Note: this type of library is considered [broken and
deprecated](https://jellyfin.org/docs/general/server/media/mixed-movies-and-shows),
so this won't be expanded any further


## Grids
Updates various grid types to use poster vs wide cards. For example,
Favorite TV Episodes will use episode images in a wide card now.

## Issues
Closes #249 
Closes #271
This commit is contained in:
damontecres 2025-11-20 17:00:11 -05:00 committed by GitHub
parent 39ad7e564f
commit 45567bf4eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 287 additions and 161 deletions

View file

@ -4,11 +4,13 @@ import android.content.Context
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.NavDrawerPinnedItem import com.github.damontecres.wholphin.data.model.NavDrawerPinnedItem
import com.github.damontecres.wholphin.data.model.NavPinType import com.github.damontecres.wholphin.data.model.NavPinType
import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem
import com.github.damontecres.wholphin.util.supportedCollectionTypes import com.github.damontecres.wholphin.util.supportedCollectionTypes
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.liveTvApi
import org.jellyfin.sdk.api.client.extensions.userViewsApi import org.jellyfin.sdk.api.client.extensions.userViewsApi
import org.jellyfin.sdk.model.api.CollectionType import org.jellyfin.sdk.model.api.CollectionType
import javax.inject.Inject import javax.inject.Inject
@ -24,21 +26,33 @@ class NavDrawerItemRepository
private val serverPreferencesDao: ServerPreferencesDao, private val serverPreferencesDao: ServerPreferencesDao,
) { ) {
suspend fun getNavDrawerItems(): List<NavDrawerItem> { suspend fun getNavDrawerItems(): List<NavDrawerItem> {
val user = serverRepository.currentUser val user = serverRepository.currentUser.value
val userViews = val userViews =
api.userViewsApi api.userViewsApi
.getUserViews(userId = user.value?.id) .getUserViews(userId = user?.id)
.content.items .content.items
val recordingFolders =
api.liveTvApi
.getRecordingFolders(userId = user?.id)
.content.items
.map { it.id }
.toSet()
val builtins = listOf(NavDrawerItem.Favorites) val builtins = listOf(NavDrawerItem.Favorites)
val libraries = val libraries =
userViews userViews
.filter { it.collectionType in supportedCollectionTypes } .filter { it.collectionType in supportedCollectionTypes || it.id in recordingFolders }
.map { .map {
val destination =
if (it.id in recordingFolders) {
Destination.Recordings(it.id)
} else {
BaseItem.from(it, api).destination()
}
ServerNavDrawerItem( ServerNavDrawerItem(
itemId = it.id, itemId = it.id,
name = it.name ?: it.id.toString(), name = it.name ?: it.id.toString(),
destination = BaseItem.from(it, api).destination(), destination = destination,
type = it.collectionType ?: CollectionType.UNKNOWN, type = it.collectionType ?: CollectionType.UNKNOWN,
) )
} }

View file

@ -42,6 +42,7 @@ import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.FavoriteWatchManager import com.github.damontecres.wholphin.services.FavoriteWatchManager
import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
import com.github.damontecres.wholphin.ui.SlimItemFields import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.cards.GridCard import com.github.damontecres.wholphin.ui.cards.GridCard
@ -96,11 +97,14 @@ class CollectionFolderViewModel
val sortAndDirection = MutableLiveData<SortAndDirection>() val sortAndDirection = MutableLiveData<SortAndDirection>()
val filter = MutableLiveData<GetItemsFilter>(GetItemsFilter()) val filter = MutableLiveData<GetItemsFilter>(GetItemsFilter())
private var useSeriesForPrimary: Boolean = true
fun init( fun init(
itemId: String, itemId: String,
initialSortAndDirection: SortAndDirection?, initialSortAndDirection: SortAndDirection?,
recursive: Boolean, recursive: Boolean,
filter: GetItemsFilter, filter: GetItemsFilter,
useSeriesForPrimary: Boolean,
): Job = ): Job =
viewModelScope.launch( viewModelScope.launch(
LoadingExceptionHandler( LoadingExceptionHandler(
@ -108,6 +112,7 @@ class CollectionFolderViewModel
context.getString(R.string.error_loading_collection, itemId), context.getString(R.string.error_loading_collection, itemId),
) + Dispatchers.IO, ) + Dispatchers.IO,
) { ) {
this@CollectionFolderViewModel.useSeriesForPrimary = useSeriesForPrimary
this@CollectionFolderViewModel.itemId = itemId this@CollectionFolderViewModel.itemId = itemId
itemId?.toUUIDOrNull()?.let { itemId?.toUUIDOrNull()?.let {
fetchItem(it) fetchItem(it)
@ -121,7 +126,7 @@ class CollectionFolderViewModel
} ?: SortAndDirection.DEFAULT } ?: SortAndDirection.DEFAULT
) )
loadResults(sortAndDirection, recursive, filter) loadResults(sortAndDirection, recursive, filter, useSeriesForPrimary)
} }
fun onSortChange( fun onSortChange(
@ -141,13 +146,14 @@ class CollectionFolderViewModel
libraryDisplayInfoDao.saveItem(libraryDisplayInfo) libraryDisplayInfoDao.saveItem(libraryDisplayInfo)
} }
} }
loadResults(sortAndDirection, recursive, filter) loadResults(sortAndDirection, recursive, filter, useSeriesForPrimary)
} }
private fun loadResults( private fun loadResults(
sortAndDirection: SortAndDirection, sortAndDirection: SortAndDirection,
recursive: Boolean, recursive: Boolean,
filter: GetItemsFilter, filter: GetItemsFilter,
useSeriesForPrimary: Boolean,
) { ) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -156,7 +162,7 @@ class CollectionFolderViewModel
this@CollectionFolderViewModel.sortAndDirection.value = sortAndDirection this@CollectionFolderViewModel.sortAndDirection.value = sortAndDirection
this@CollectionFolderViewModel.filter.value = filter this@CollectionFolderViewModel.filter.value = filter
} }
val newPager = createPager(sortAndDirection, recursive, filter) val newPager = createPager(sortAndDirection, recursive, filter, useSeriesForPrimary)
newPager.init() newPager.init()
if (newPager.isNotEmpty()) newPager.getBlocking(0) if (newPager.isNotEmpty()) newPager.getBlocking(0)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -170,6 +176,7 @@ class CollectionFolderViewModel
sortAndDirection: SortAndDirection, sortAndDirection: SortAndDirection,
recursive: Boolean, recursive: Boolean,
filter: GetItemsFilter, filter: GetItemsFilter,
useSeriesForPrimary: Boolean,
): ApiRequestPager<out Any> { ): ApiRequestPager<out Any> {
val item = item.value val item = item.value
return when (filter.override) { return when (filter.override) {
@ -228,7 +235,7 @@ class CollectionFolderViewModel
request, request,
GetItemsRequestHandler, GetItemsRequestHandler,
viewModelScope, viewModelScope,
useSeriesForPrimary = true, useSeriesForPrimary = useSeriesForPrimary,
) )
newPager newPager
} }
@ -246,7 +253,7 @@ class CollectionFolderViewModel
request, request,
GetPersonsHandler, GetPersonsHandler,
viewModelScope, viewModelScope,
useSeriesForPrimary = true, useSeriesForPrimary = useSeriesForPrimary,
) )
newPager newPager
} }
@ -344,10 +351,17 @@ fun CollectionFolderGrid(
showTitle: Boolean = true, showTitle: Boolean = true,
positionCallback: ((columns: Int, position: Int) -> Unit)? = null, positionCallback: ((columns: Int, position: Int) -> Unit)? = null,
params: CollectionFolderGridParameters = CollectionFolderGridParameters(), params: CollectionFolderGridParameters = CollectionFolderGridParameters(),
useSeriesForPrimary: Boolean = true,
) { ) {
val context = LocalContext.current val context = LocalContext.current
OneTimeLaunchedEffect { OneTimeLaunchedEffect {
viewModel.init(itemId, initialSortAndDirection, recursive, initialFilter) viewModel.init(
itemId,
initialSortAndDirection,
recursive,
initialFilter,
useSeriesForPrimary,
)
} }
val sortAndDirection by viewModel.sortAndDirection.observeAsState() val sortAndDirection by viewModel.sortAndDirection.observeAsState()
val filter by viewModel.filter.observeAsState(initialFilter) val filter by viewModel.filter.observeAsState(initialFilter)
@ -538,4 +552,52 @@ data class CollectionFolderGridParameters(
modifier = mod, modifier = mod,
) )
}, },
) ) {
companion object {
val POSTER =
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,
)
},
)
val WIDE =
CollectionFolderGridParameters(
columns = 4,
spacing = 24.dp,
cardContent = { item, onClick, onLongClick, mod ->
GridCard(
item = item,
onClick = onClick,
onLongClick = onLongClick,
imageContentScale = ContentScale.Crop,
imageAspectRatio = AspectRatios.WIDE,
modifier = mod,
)
},
)
val SQUARE =
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,
)
},
)
}
}

View file

@ -7,14 +7,11 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
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
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.GetItemsFilter import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.preferences.UserPreferences 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.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.CollectionFolderGridParameters import com.github.damontecres.wholphin.ui.components.CollectionFolderGridParameters
import com.github.damontecres.wholphin.ui.data.MovieSortOptions import com.github.damontecres.wholphin.ui.data.MovieSortOptions
@ -32,21 +29,6 @@ fun CollectionFolderBoxSet(
preferencesViewModel: PreferencesViewModel = hiltViewModel(), preferencesViewModel: PreferencesViewModel = hiltViewModel(),
) { ) {
var showHeader by remember { mutableStateOf(true) } 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( CollectionFolderGrid(
preferences = preferences, preferences = preferences,
onClickItem = { _, item -> preferencesViewModel.navigationManager.navigateTo(item.destination()) }, onClickItem = { _, item -> preferencesViewModel.navigationManager.navigateTo(item.destination()) },
@ -61,6 +43,6 @@ fun CollectionFolderBoxSet(
positionCallback = { columns, position -> positionCallback = { columns, position ->
showHeader = position < columns showHeader = position < columns
}, },
params = params, params = CollectionFolderGridParameters.POSTER,
) )
} }

View file

@ -7,14 +7,10 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
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
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.GetItemsFilter import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.preferences.UserPreferences 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.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.CollectionFolderGridParameters import com.github.damontecres.wholphin.ui.components.CollectionFolderGridParameters
import com.github.damontecres.wholphin.ui.data.VideoSortOptions import com.github.damontecres.wholphin.ui.data.VideoSortOptions
@ -25,7 +21,7 @@ import java.util.UUID
fun CollectionFolderGeneric( fun CollectionFolderGeneric(
preferences: UserPreferences, preferences: UserPreferences,
itemId: UUID, itemId: UUID,
item: BaseItem?, usePosters: Boolean,
recursive: Boolean, recursive: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
filter: GetItemsFilter = GetItemsFilter(), filter: GetItemsFilter = GetItemsFilter(),
@ -33,20 +29,13 @@ fun CollectionFolderGeneric(
) { ) {
var showHeader by remember { mutableStateOf(true) } var showHeader by remember { mutableStateOf(true) }
val params = val params =
CollectionFolderGridParameters( remember(usePosters) {
columns = 4, if (usePosters) {
spacing = 24.dp, CollectionFolderGridParameters.POSTER
cardContent = { item, onClick, onLongClick, mod -> } else {
GridCard( CollectionFolderGridParameters.WIDE
item = item, }
onClick = onClick, }
onLongClick = onLongClick,
imageContentScale = ContentScale.Crop,
imageAspectRatio = AspectRatios.WIDE,
modifier = mod,
)
},
)
CollectionFolderGrid( CollectionFolderGrid(
preferences = preferences, preferences = preferences,
onClickItem = { _, item -> preferencesViewModel.navigationManager.navigateTo(item.destination()) }, onClickItem = { _, item -> preferencesViewModel.navigationManager.navigateTo(item.destination()) },

View file

@ -160,7 +160,7 @@ fun CollectionFolderLiveTv(
onClickItem = onClickItem, onClickItem = onClickItem,
itemId = folders[folderIndex].id, itemId = folders[folderIndex].id,
initialFilter = GetItemsFilter(), initialFilter = GetItemsFilter(),
showTitle = showHeader, showTitle = false,
recursive = false, recursive = false,
sortOptions = VideoSortOptions, sortOptions = VideoSortOptions,
modifier = modifier =

View file

@ -7,14 +7,11 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
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
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.GetItemsFilter import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.preferences.UserPreferences 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.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.CollectionFolderGridParameters import com.github.damontecres.wholphin.ui.components.CollectionFolderGridParameters
import com.github.damontecres.wholphin.ui.data.PlaylistSortOptions import com.github.damontecres.wholphin.ui.data.PlaylistSortOptions
@ -32,21 +29,6 @@ fun CollectionFolderPlaylist(
preferencesViewModel: PreferencesViewModel = hiltViewModel(), preferencesViewModel: PreferencesViewModel = hiltViewModel(),
) { ) {
var showHeader by remember { mutableStateOf(true) } 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( CollectionFolderGrid(
preferences = preferences, preferences = preferences,
onClickItem = { _, item -> preferencesViewModel.navigationManager.navigateTo(item.destination()) }, onClickItem = { _, item -> preferencesViewModel.navigationManager.navigateTo(item.destination()) },
@ -61,6 +43,6 @@ fun CollectionFolderPlaylist(
positionCallback = { columns, position -> positionCallback = { columns, position ->
showHeader = position < columns showHeader = position < columns
}, },
params = params, params = CollectionFolderGridParameters.SQUARE,
) )
} }

View file

@ -0,0 +1,46 @@
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.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.preferences.UserPreferences
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 CollectionFolderRecordings(
preferences: UserPreferences,
itemId: UUID,
recursive: Boolean,
modifier: Modifier = Modifier,
filter: GetItemsFilter = GetItemsFilter(),
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
) {
var showHeader by remember { mutableStateOf(true) }
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 = CollectionFolderGridParameters.POSTER,
)
}

View file

@ -28,6 +28,7 @@ import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.data.model.GetItemsFilterOverride import com.github.damontecres.wholphin.data.model.GetItemsFilterOverride
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.CollectionFolderGridParameters
import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.TabRow import com.github.damontecres.wholphin.ui.components.TabRow
import com.github.damontecres.wholphin.ui.data.EpisodeSortOptions import com.github.damontecres.wholphin.ui.data.EpisodeSortOptions
@ -163,6 +164,8 @@ fun FavoritesPage(
showTitle = false, showTitle = false,
recursive = true, recursive = true,
sortOptions = EpisodeSortOptions, sortOptions = EpisodeSortOptions,
params = CollectionFolderGridParameters.WIDE,
useSeriesForPrimary = false,
modifier = modifier =
Modifier Modifier
.padding(start = 16.dp) .padding(start = 16.dp)
@ -186,6 +189,7 @@ fun FavoritesPage(
showTitle = false, showTitle = false,
recursive = true, recursive = true,
sortOptions = VideoSortOptions, sortOptions = VideoSortOptions,
params = CollectionFolderGridParameters.WIDE,
modifier = modifier =
Modifier Modifier
.padding(start = 16.dp) .padding(start = 16.dp)
@ -209,6 +213,7 @@ fun FavoritesPage(
showTitle = false, showTitle = false,
recursive = true, recursive = true,
sortOptions = VideoSortOptions, sortOptions = VideoSortOptions,
params = CollectionFolderGridParameters.SQUARE,
modifier = modifier =
Modifier Modifier
.padding(start = 16.dp) .padding(start = 16.dp)

View file

@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -453,8 +454,16 @@ fun TvGuideGridContent(
Box( Box(
modifier = modifier =
Modifier Modifier
// Intentionally set background twice
// The second is padded so there are gaps between times
// The first covers those gaps
.background(MaterialTheme.colorScheme.background)
.padding(horizontal = 2.dp)
.fillMaxSize() .fillMaxSize()
.background(MaterialTheme.colorScheme.surface), .background(
MaterialTheme.colorScheme.surfaceVariant,
shape = RoundedCornerShape(4.dp),
),
) { ) {
val differentDay = val differentDay =
start.toLocalDate() != start.toLocalDate() !=
@ -472,7 +481,8 @@ fun TvGuideGridContent(
) )
Text( Text(
text = time.toString(), text = time.toString(),
modifier = Modifier.background(MaterialTheme.colorScheme.background), color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.padding(2.dp),
) )
} }
} }

View file

@ -217,14 +217,12 @@ class HomeViewModel
views.items views.items
.filter { .filter {
it.id in includedIds && it.id !in excluded && it.id in includedIds && it.id !in excluded &&
it.collectionType in supportedLatestCollectionTypes // Exclude Live TV because a recording folder view will be used instead
it.collectionType != CollectionType.LIVETV
}.mapNotNull { view -> }.mapNotNull { view ->
val title = val title =
if (view.collectionType == CollectionType.LIVETV) {
context.getString(R.string.recently_recorded)
} else {
view.name?.let { context.getString(R.string.recently_added_in, it) } view.name?.let { context.getString(R.string.recently_added_in, it) }
} ?: context.getString(R.string.recently_added) ?: context.getString(R.string.recently_added)
val viewId = val viewId =
if (view.collectionType == CollectionType.LIVETV) { if (view.collectionType == CollectionType.LIVETV) {
api.liveTvApi api.liveTvApi

View file

@ -69,6 +69,11 @@ sealed class Destination(
"MediaItem(itemId=$itemId, type=$type, seasonEpisode=$seasonEpisode, collectionType=${item?.data?.collectionType})" "MediaItem(itemId=$itemId, type=$type, seasonEpisode=$seasonEpisode, collectionType=${item?.data?.collectionType})"
} }
@Serializable
data class Recordings(
val itemId: UUID,
) : Destination()
@Serializable @Serializable
data class Playback( data class Playback(
val itemId: UUID, val itemId: UUID,

View file

@ -11,6 +11,7 @@ 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.CollectionFolderPlaylist
import com.github.damontecres.wholphin.ui.detail.CollectionFolderRecordings
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
@ -107,97 +108,40 @@ fun DestinationContent(
modifier, modifier,
) )
BaseItemKind.COLLECTION_FOLDER -> {
when (destination.item?.data?.collectionType) {
CollectionType.TVSHOWS ->
CollectionFolderTv(
preferences,
destination,
modifier,
)
CollectionType.MOVIES ->
CollectionFolderMovie(
preferences,
destination,
modifier,
)
CollectionType.BOXSETS ->
CollectionFolderBoxSet(
preferences,
destination.itemId,
destination.item,
false,
modifier,
)
else ->
CollectionFolderGeneric(
preferences,
destination.itemId,
destination.item,
false,
modifier,
)
}
}
BaseItemKind.PLAYLIST -> BaseItemKind.PLAYLIST ->
PlaylistDetails( PlaylistDetails(
destination = destination, destination = destination,
modifier = modifier, modifier = modifier,
) )
BaseItemKind.USER_VIEW -> BaseItemKind.COLLECTION_FOLDER ->
when (destination.item?.data?.collectionType) { CollectionFolder(
CollectionType.TVSHOWS -> preferences = preferences,
CollectionFolderTv( destination = destination,
preferences, collectionType = destination.item?.data?.collectionType,
destination, usePostersOverride = null,
modifier, recursiveOverride = null,
modifier = modifier,
) )
CollectionType.MOVIES ->
CollectionFolderMovie(
preferences,
destination,
modifier,
)
CollectionType.LIVETV ->
CollectionFolderLiveTv(
preferences,
destination,
modifier,
)
CollectionType.PLAYLISTS ->
CollectionFolderPlaylist(
preferences,
destination.itemId,
destination.item,
true,
modifier,
)
else ->
CollectionFolderGeneric(
preferences,
destination.itemId,
destination.item,
true,
modifier,
)
}
BaseItemKind.FOLDER -> BaseItemKind.FOLDER ->
CollectionFolderGeneric( CollectionFolder(
preferences, preferences = preferences,
destination.itemId, destination = destination,
destination.item, collectionType = destination.item?.data?.collectionType,
false, usePostersOverride = true,
modifier, recursiveOverride = null,
modifier = modifier,
)
BaseItemKind.USER_VIEW ->
CollectionFolder(
preferences = preferences,
destination = destination,
collectionType = destination.item?.data?.collectionType,
usePostersOverride = null,
recursiveOverride = true,
modifier = modifier,
) )
BaseItemKind.PERSON -> BaseItemKind.PERSON ->
@ -217,12 +161,20 @@ fun DestinationContent(
CollectionFolderGeneric( CollectionFolderGeneric(
preferences = preferences, preferences = preferences,
itemId = destination.itemId, itemId = destination.itemId,
item = null,
filter = destination.filter, filter = destination.filter,
recursive = destination.recursive, recursive = destination.recursive,
usePosters = true,
modifier = modifier, modifier = modifier,
) )
is Destination.Recordings ->
CollectionFolderRecordings(
preferences,
destination.itemId,
false,
modifier,
)
is Destination.ItemGrid -> is Destination.ItemGrid ->
ItemGrid( ItemGrid(
destination, destination,
@ -248,3 +200,81 @@ fun DestinationContent(
Destination.Debug -> DebugPage(preferences, modifier) Destination.Debug -> DebugPage(preferences, modifier)
} }
} }
@Composable
fun CollectionFolder(
preferences: UserPreferences,
destination: Destination.MediaItem,
collectionType: CollectionType?,
usePostersOverride: Boolean?,
recursiveOverride: Boolean?,
modifier: Modifier = Modifier,
) {
when (collectionType) {
CollectionType.TVSHOWS ->
CollectionFolderTv(
preferences,
destination,
modifier,
)
CollectionType.MOVIES ->
CollectionFolderMovie(
preferences,
destination,
modifier,
)
CollectionType.BOXSETS ->
CollectionFolderBoxSet(
preferences,
destination.itemId,
destination.item,
false,
modifier,
)
CollectionType.PLAYLISTS ->
CollectionFolderPlaylist(
preferences,
destination.itemId,
destination.item,
true,
modifier,
)
CollectionType.LIVETV ->
CollectionFolderLiveTv(
preferences = preferences,
destination = destination,
modifier = modifier,
)
CollectionType.HOMEVIDEOS,
CollectionType.MUSICVIDEOS,
CollectionType.MUSIC,
CollectionType.BOOKS,
CollectionType.PHOTOS,
->
CollectionFolderGeneric(
preferences,
destination.itemId,
usePosters = usePostersOverride ?: false,
recursive = recursiveOverride ?: false,
modifier = modifier,
)
CollectionType.FOLDERS,
CollectionType.TRAILERS,
CollectionType.UNKNOWN,
null,
->
CollectionFolderGeneric(
preferences,
destination.itemId,
usePosters = usePostersOverride ?: false,
recursive = recursiveOverride ?: false,
modifier = modifier,
)
}
}

View file

@ -29,6 +29,9 @@ val supportedCollectionTypes =
CollectionType.PLAYLISTS, CollectionType.PLAYLISTS,
CollectionType.BOXSETS, CollectionType.BOXSETS,
CollectionType.LIVETV, CollectionType.LIVETV,
CollectionType.MUSICVIDEOS,
CollectionType.FOLDERS,
null, // Mixed
) )
val supportedPlayableTypes = val supportedPlayableTypes =