Add tabs for tv/movie libraries w/ recommended

This commit is contained in:
Damontecres 2025-10-06 18:45:32 -04:00
parent 1a4ddabff9
commit 126530ab73
No known key found for this signature in database
10 changed files with 606 additions and 64 deletions

View file

@ -0,0 +1,152 @@
package com.github.damontecres.dolphin.ui.components
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.DefaultItemFields
import com.github.damontecres.dolphin.ui.OneTimeLaunchedEffect
import com.github.damontecres.dolphin.ui.main.HomePageContent
import com.github.damontecres.dolphin.ui.main.HomeRow
import com.github.damontecres.dolphin.ui.main.HomeSection
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.util.ApiRequestPager
import com.github.damontecres.dolphin.util.ExceptionHandler
import com.github.damontecres.dolphin.util.GetItemsRequestHandler
import com.github.damontecres.dolphin.util.GetResumeItemsRequestHandler
import com.github.damontecres.dolphin.util.GetSuggestionsRequestHandler
import com.github.damontecres.dolphin.util.LoadingState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ItemSortBy
import org.jellyfin.sdk.model.api.SortOrder
import org.jellyfin.sdk.model.api.request.GetItemsRequest
import org.jellyfin.sdk.model.api.request.GetResumeItemsRequest
import org.jellyfin.sdk.model.api.request.GetSuggestionsRequest
import java.util.UUID
import javax.inject.Inject
@HiltViewModel
class RecommendedMovieViewModel
@Inject
constructor(
private val api: ApiClient,
) : ViewModel() {
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
val rows = MutableLiveData<List<HomeRow>>()
fun init(
preferences: UserPreferences,
parentId: UUID,
) {
viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) {
val resumeItemsRequest =
GetResumeItemsRequest(
parentId = parentId,
fields = DefaultItemFields,
includeItemTypes = listOf(BaseItemKind.MOVIE),
enableUserData = true,
)
val resumeItems =
ApiRequestPager(api, resumeItemsRequest, GetResumeItemsRequestHandler, viewModelScope)
val recentlyReleasedRequest =
GetItemsRequest(
parentId = parentId,
fields = DefaultItemFields,
includeItemTypes = listOf(BaseItemKind.MOVIE),
recursive = true,
enableUserData = true,
sortBy = listOf(ItemSortBy.PREMIERE_DATE),
sortOrder = listOf(SortOrder.DESCENDING),
)
val recentlyReleasedItems =
ApiRequestPager(api, recentlyReleasedRequest, GetItemsRequestHandler, viewModelScope)
val recentlyAddedRequest =
GetItemsRequest(
parentId = parentId,
fields = DefaultItemFields,
includeItemTypes = listOf(BaseItemKind.MOVIE),
recursive = true,
enableUserData = true,
sortBy = listOf(ItemSortBy.DATE_CREATED),
sortOrder = listOf(SortOrder.DESCENDING),
)
val recentlyAddedItems =
ApiRequestPager(api, recentlyAddedRequest, GetItemsRequestHandler, viewModelScope)
val suggestionsRequest =
GetSuggestionsRequest(
type = listOf(BaseItemKind.MOVIE),
)
val suggestedItems = ApiRequestPager(api, suggestionsRequest, GetSuggestionsRequestHandler, viewModelScope)
val unwatchedTopRatedRequest =
GetItemsRequest(
parentId = parentId,
fields = DefaultItemFields,
includeItemTypes = listOf(BaseItemKind.MOVIE),
recursive = true,
enableUserData = true,
isPlayed = false,
sortBy = listOf(ItemSortBy.COMMUNITY_RATING),
sortOrder = listOf(SortOrder.DESCENDING),
)
val unwatchedTopRatedItems =
ApiRequestPager(api, unwatchedTopRatedRequest, GetItemsRequestHandler, viewModelScope, useSeriesForPrimary = true)
val rows = listOf(resumeItems, recentlyReleasedItems, recentlyAddedItems, suggestedItems, unwatchedTopRatedItems)
rows.forEach { it.init() }
val homeRows =
listOf(
HomeRow(HomeSection.RESUME, resumeItems, "Continue Watching"),
HomeRow(HomeSection.LATEST_MEDIA, recentlyReleasedItems, "Recently Released"),
HomeRow(HomeSection.LATEST_MEDIA, recentlyAddedItems, "Recently Added"),
HomeRow(HomeSection.NONE, suggestedItems, "Suggestions"),
HomeRow(HomeSection.NONE, unwatchedTopRatedItems, "Top Rated Unwatched"),
)
withContext(Dispatchers.Main) {
this@RecommendedMovieViewModel.rows.value = homeRows
loading.value = LoadingState.Success
}
}
}
}
@Composable
fun RecommendedMovie(
preferences: UserPreferences,
navigationManager: NavigationManager,
parentId: UUID,
modifier: Modifier,
viewModel: RecommendedMovieViewModel = hiltViewModel(),
) {
OneTimeLaunchedEffect {
viewModel.init(preferences, parentId)
}
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
val rows by viewModel.rows.observeAsState(listOf())
when (val state = loading) {
is LoadingState.Error -> ErrorMessage(state)
LoadingState.Loading -> LoadingPage()
LoadingState.Success ->
HomePageContent(
navigationManager = navigationManager,
homeRows = rows,
modifier = modifier,
)
}
}

View file

@ -0,0 +1,164 @@
package com.github.damontecres.dolphin.ui.components
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.DefaultItemFields
import com.github.damontecres.dolphin.ui.OneTimeLaunchedEffect
import com.github.damontecres.dolphin.ui.main.HomePageContent
import com.github.damontecres.dolphin.ui.main.HomeRow
import com.github.damontecres.dolphin.ui.main.HomeSection
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.util.ApiRequestPager
import com.github.damontecres.dolphin.util.ExceptionHandler
import com.github.damontecres.dolphin.util.GetItemsRequestHandler
import com.github.damontecres.dolphin.util.GetNextUpRequestHandler
import com.github.damontecres.dolphin.util.GetResumeItemsRequestHandler
import com.github.damontecres.dolphin.util.GetSuggestionsRequestHandler
import com.github.damontecres.dolphin.util.LoadingState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ItemSortBy
import org.jellyfin.sdk.model.api.SortOrder
import org.jellyfin.sdk.model.api.request.GetItemsRequest
import org.jellyfin.sdk.model.api.request.GetNextUpRequest
import org.jellyfin.sdk.model.api.request.GetResumeItemsRequest
import org.jellyfin.sdk.model.api.request.GetSuggestionsRequest
import java.util.UUID
import javax.inject.Inject
@HiltViewModel
class RecommendedTvShowViewModel
@Inject
constructor(
private val api: ApiClient,
) : ViewModel() {
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
val rows = MutableLiveData<List<HomeRow>>()
fun init(
preferences: UserPreferences,
parentId: UUID,
) {
viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) {
val resumeItemsRequest =
GetResumeItemsRequest(
parentId = parentId,
fields = DefaultItemFields,
includeItemTypes = listOf(BaseItemKind.EPISODE),
enableUserData = true,
)
val resumeItems =
ApiRequestPager(api, resumeItemsRequest, GetResumeItemsRequestHandler, viewModelScope, useSeriesForPrimary = true)
val nextUpRequest =
GetNextUpRequest(
parentId = parentId,
fields = DefaultItemFields,
enableUserData = true,
)
val nextUpItems = ApiRequestPager(api, nextUpRequest, GetNextUpRequestHandler, viewModelScope, useSeriesForPrimary = true)
val recentlyReleasedRequest =
GetItemsRequest(
parentId = parentId,
fields = DefaultItemFields,
includeItemTypes = listOf(BaseItemKind.EPISODE),
recursive = true,
enableUserData = true,
sortBy = listOf(ItemSortBy.PREMIERE_DATE),
sortOrder = listOf(SortOrder.DESCENDING),
)
val recentlyReleasedItems =
ApiRequestPager(api, recentlyReleasedRequest, GetItemsRequestHandler, viewModelScope, useSeriesForPrimary = true)
val recentlyAddedRequest =
GetItemsRequest(
parentId = parentId,
fields = DefaultItemFields,
includeItemTypes = listOf(BaseItemKind.EPISODE),
recursive = true,
enableUserData = true,
sortBy = listOf(ItemSortBy.DATE_CREATED),
sortOrder = listOf(SortOrder.DESCENDING),
)
val recentlyAddedItems =
ApiRequestPager(api, recentlyAddedRequest, GetItemsRequestHandler, viewModelScope, useSeriesForPrimary = true)
val suggestionsRequest =
GetSuggestionsRequest(
type = listOf(BaseItemKind.SERIES),
)
val suggestedItems = ApiRequestPager(api, suggestionsRequest, GetSuggestionsRequestHandler, viewModelScope)
val unwatchedTopRatedRequest =
GetItemsRequest(
parentId = parentId,
fields = DefaultItemFields,
includeItemTypes = listOf(BaseItemKind.SERIES),
recursive = true,
enableUserData = true,
isPlayed = false,
sortBy = listOf(ItemSortBy.COMMUNITY_RATING),
sortOrder = listOf(SortOrder.DESCENDING),
)
val unwatchedTopRatedItems =
ApiRequestPager(api, unwatchedTopRatedRequest, GetItemsRequestHandler, viewModelScope, useSeriesForPrimary = true)
val rows =
listOf(resumeItems, nextUpItems, recentlyReleasedItems, recentlyAddedItems, suggestedItems, unwatchedTopRatedItems)
rows.forEach { it.init() }
val homeRows =
listOf(
HomeRow(HomeSection.RESUME, resumeItems, "Continue Watching"),
HomeRow(HomeSection.NEXT_UP, nextUpItems, "Next Up"),
HomeRow(HomeSection.LATEST_MEDIA, recentlyReleasedItems, "Recently Released"),
HomeRow(HomeSection.LATEST_MEDIA, recentlyAddedItems, "Recently Added"),
HomeRow(HomeSection.NONE, suggestedItems, "Suggestions"),
HomeRow(HomeSection.NONE, unwatchedTopRatedItems, "Top Rated Unwatched"),
)
withContext(Dispatchers.Main) {
this@RecommendedTvShowViewModel.rows.value = homeRows
loading.value = LoadingState.Success
}
}
}
}
@Composable
fun RecommendedTvShow(
preferences: UserPreferences,
navigationManager: NavigationManager,
parentId: UUID,
modifier: Modifier,
viewModel: RecommendedTvShowViewModel = hiltViewModel(),
) {
OneTimeLaunchedEffect {
viewModel.init(preferences, parentId)
}
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
val rows by viewModel.rows.observeAsState(listOf())
when (val state = loading) {
is LoadingState.Error -> ErrorMessage(state)
LoadingState.Loading -> LoadingPage()
LoadingState.Success ->
HomePageContent(
navigationManager = navigationManager,
homeRows = rows,
modifier = modifier,
)
}
}

View file

@ -135,6 +135,7 @@ fun CollectionFolderDetails(
ItemSortBy.SORT_NAME,
SortOrder.ASCENDING,
),
showTitle: Boolean = true,
) {
OneTimeLaunchedEffect {
viewModel.init(destination.itemId, destination.item, initialSortAndDirection)
@ -161,6 +162,7 @@ fun CollectionFolderDetails(
onSortChange = {
viewModel.loadResults(it)
},
showTitle = showTitle,
)
}
}
@ -177,6 +179,7 @@ fun CollectionDetails(
sortAndDirection: SortAndDirection,
onSortChange: (SortAndDirection) -> Unit,
modifier: Modifier = Modifier,
showTitle: Boolean = true,
) {
val title = library.name ?: item.data.name ?: item.data.collectionType?.name ?: "Collection"
val sortOptions =
@ -193,13 +196,15 @@ fun CollectionDetails(
verticalArrangement = Arrangement.spacedBy(0.dp),
modifier = modifier,
) {
Text(
text = title,
style = MaterialTheme.typography.displayMedium,
color = MaterialTheme.colorScheme.onBackground,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(),
)
if (showTitle) {
Text(
text = title,
style = MaterialTheme.typography.displayMedium,
color = MaterialTheme.colorScheme.onBackground,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(),
)
}
SortByButton(
sortOptions = sortOptions,
current = sortAndDirection,

View file

@ -0,0 +1,100 @@
package com.github.damontecres.dolphin.ui.detail
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusRestorer
import androidx.compose.ui.unit.dp
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Tab
import androidx.tv.material3.TabRow
import androidx.tv.material3.Text
import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.components.ErrorMessage
import com.github.damontecres.dolphin.ui.components.RecommendedMovie
import com.github.damontecres.dolphin.ui.ifElse
import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.ui.tryRequestFocus
@Composable
fun CollectionFolderMovie(
preferences: UserPreferences,
navigationManager: NavigationManager,
destination: Destination.MediaItem,
modifier: Modifier = Modifier,
) {
val tabs = listOf("Recommended", "Library")
var selectedTabIndex by rememberSaveable { mutableIntStateOf(0) }
val focusRequester = remember { FocusRequester() }
val firstTabFocusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
Column(
modifier = modifier,
) {
TabRow(
selectedTabIndex = selectedTabIndex,
modifier =
Modifier
.padding(start = 32.dp, top = 16.dp)
.focusRestorer(firstTabFocusRequester),
tabs = {
tabs.forEachIndexed { index, title ->
Tab(
selected = selectedTabIndex == index,
onClick = { selectedTabIndex = index },
onFocus = {},
) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
modifier =
Modifier
.padding(8.dp)
.ifElse(index == 0, Modifier.focusRequester(firstTabFocusRequester)),
)
}
}
},
)
when (selectedTabIndex) {
0 -> {
RecommendedMovie(
preferences = preferences,
navigationManager = navigationManager,
parentId = destination.itemId,
modifier =
Modifier
.padding(start = 16.dp)
.fillMaxSize()
.focusRequester(focusRequester),
)
}
1 -> {
CollectionFolderDetails(
preferences = preferences,
navigationManager = navigationManager,
destination = destination,
showTitle = false,
modifier =
Modifier
.padding(start = 16.dp)
.fillMaxSize()
.focusRequester(focusRequester),
)
}
else -> ErrorMessage("Invalid tab index $selectedTabIndex", null)
}
}
}

View file

@ -0,0 +1,100 @@
package com.github.damontecres.dolphin.ui.detail
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusRestorer
import androidx.compose.ui.unit.dp
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Tab
import androidx.tv.material3.TabRow
import androidx.tv.material3.Text
import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.components.ErrorMessage
import com.github.damontecres.dolphin.ui.components.RecommendedTvShow
import com.github.damontecres.dolphin.ui.ifElse
import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.ui.tryRequestFocus
@Composable
fun CollectionFolderTv(
preferences: UserPreferences,
navigationManager: NavigationManager,
destination: Destination.MediaItem,
modifier: Modifier = Modifier,
) {
val tabs = listOf("Recommended", "Library")
var selectedTabIndex by rememberSaveable { mutableIntStateOf(0) }
val focusRequester = remember { FocusRequester() }
val firstTabFocusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
Column(
modifier = modifier,
) {
TabRow(
selectedTabIndex = selectedTabIndex,
modifier =
Modifier
.padding(start = 32.dp, top = 16.dp)
.focusRestorer(firstTabFocusRequester),
tabs = {
tabs.forEachIndexed { index, title ->
Tab(
selected = selectedTabIndex == index,
onClick = { selectedTabIndex = index },
onFocus = {},
) {
Text(
text = title,
style = MaterialTheme.typography.titleMedium,
modifier =
Modifier
.padding(8.dp)
.ifElse(index == 0, Modifier.focusRequester(firstTabFocusRequester)),
)
}
}
},
)
when (selectedTabIndex) {
0 -> {
RecommendedTvShow(
preferences = preferences,
navigationManager = navigationManager,
parentId = destination.itemId,
modifier =
Modifier
.padding(start = 16.dp)
.fillMaxSize()
.focusRequester(focusRequester),
)
}
1 -> {
CollectionFolderDetails(
preferences = preferences,
navigationManager = navigationManager,
destination = destination,
showTitle = false,
modifier =
Modifier
.padding(start = 16.dp)
.fillMaxSize()
.focusRequester(focusRequester),
)
}
else -> ErrorMessage("Invalid tab index $selectedTabIndex", null)
}
}
}

View file

@ -163,44 +163,46 @@ fun HomePageContent(
modifier = Modifier,
) {
itemsIndexed(homeRows) { rowIndex, row ->
ItemRow(
title = row.title ?: stringResource(row.section.nameRes),
items = row.items,
onClickItem = {
navigationManager.navigateTo(it.destination())
},
cardOnFocus = { isFocused, index ->
if (isFocused) {
focusedItem = row.items.getOrNull(index)
position = RowColumn(rowIndex, index)
}
},
onLongClickItem = {},
modifier = Modifier.fillMaxWidth(),
cardContent = { index, item, modifier, onClick, onLongClick ->
// TODO better aspect ration handling?
BannerCard(
imageUrl = item?.imageUrl,
aspectRatio = (2f / 3f),
cornerText = item?.data?.indexNumber?.let { "E$it" },
played = item?.data?.userData?.played ?: false,
playPercent = item?.data?.userData?.playedPercentage ?: 0.0,
onClick = onClick,
onLongClick = onLongClick,
modifier =
modifier
.ifElse(
focusedItem == item,
Modifier.focusRequester(focusRequester),
).ifElse(
RowColumn(rowIndex, index) == position,
Modifier.focusRequester(positionFocusRequester),
),
interactionSource = null,
cardHeight = 200.dp,
)
},
)
if (row.items.isNotEmpty()) {
ItemRow(
title = row.title ?: stringResource(row.section.nameRes),
items = row.items,
onClickItem = {
navigationManager.navigateTo(it.destination())
},
cardOnFocus = { isFocused, index ->
if (isFocused) {
focusedItem = row.items.getOrNull(index)
position = RowColumn(rowIndex, index)
}
},
onLongClickItem = {},
modifier = Modifier.fillMaxWidth(),
cardContent = { index, item, modifier, onClick, onLongClick ->
// TODO better aspect ration handling?
BannerCard(
imageUrl = item?.imageUrl,
aspectRatio = (2f / 3f),
cornerText = item?.data?.indexNumber?.let { "E$it" },
played = item?.data?.userData?.played ?: false,
playPercent = item?.data?.userData?.playedPercentage ?: 0.0,
onClick = onClick,
onLongClick = onLongClick,
modifier =
modifier
.ifElse(
focusedItem == item,
Modifier.focusRequester(focusRequester),
).ifElse(
RowColumn(rowIndex, index) == position,
Modifier.focusRequester(positionFocusRequester),
),
interactionSource = null,
cardHeight = 200.dp,
)
},
)
}
}
}
}

View file

@ -53,7 +53,8 @@ sealed class Destination(
@Transient val item: BaseItem? = null,
val seasonEpisode: SeasonEpisode? = null,
) : Destination() {
override fun toString(): String = "MediaItem(itemId=$itemId, type=$type, seasonEpisode=$seasonEpisode)"
override fun toString(): String =
"MediaItem(itemId=$itemId, type=$type, seasonEpisode=$seasonEpisode, collectionType=${item?.data?.collectionType})"
}
@Serializable

View file

@ -6,6 +6,8 @@ import androidx.tv.material3.Text
import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.components.LicenseInfo
import com.github.damontecres.dolphin.ui.detail.CollectionFolderDetails
import com.github.damontecres.dolphin.ui.detail.CollectionFolderMovie
import com.github.damontecres.dolphin.ui.detail.CollectionFolderTv
import com.github.damontecres.dolphin.ui.detail.EpisodeDetails
import com.github.damontecres.dolphin.ui.detail.SeasonDetails
import com.github.damontecres.dolphin.ui.detail.SeriesDetails
@ -20,6 +22,7 @@ import com.github.damontecres.dolphin.ui.preferences.PreferencesPage
import com.github.damontecres.dolphin.ui.setup.SwitchServerContent
import com.github.damontecres.dolphin.ui.setup.SwitchUserContent
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.CollectionType
import org.jellyfin.sdk.model.api.DeviceProfile
@Composable
@ -110,12 +113,31 @@ fun DestinationContent(
)
BaseItemKind.COLLECTION_FOLDER -> {
CollectionFolderDetails(
preferences,
navigationManager,
destination,
modifier,
)
when (destination.item?.data?.collectionType) {
CollectionType.TVSHOWS ->
CollectionFolderTv(
preferences,
navigationManager,
destination,
modifier,
)
CollectionType.MOVIES ->
CollectionFolderMovie(
preferences,
navigationManager,
destination,
modifier,
)
else ->
CollectionFolderDetails(
preferences,
navigationManager,
destination,
modifier,
)
}
}
else -> {

View file

@ -53,7 +53,7 @@ import com.github.damontecres.dolphin.R
import com.github.damontecres.dolphin.data.JellyfinServer
import com.github.damontecres.dolphin.data.JellyfinUser
import com.github.damontecres.dolphin.data.ServerRepository
import com.github.damontecres.dolphin.data.model.Library
import com.github.damontecres.dolphin.data.model.BaseItem
import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.FontAwesome
import com.github.damontecres.dolphin.util.ExceptionHandler
@ -72,7 +72,7 @@ class NavDrawerViewModel
val serverRepository: ServerRepository,
val api: ApiClient,
) : ViewModel() {
val libraries = MutableLiveData<List<Library>>(listOf())
val libraries = MutableLiveData<List<BaseItem>>(listOf())
init {
viewModelScope.launch(ExceptionHandler()) {
@ -81,7 +81,7 @@ class NavDrawerViewModel
.getUserViews()
.content.items
// Timber.v("userViews: $userViews")
libraries.value = userViews.map { Library.fromDto(it, api) }
libraries.value = userViews.map { BaseItem.from(it, api) }
}
}
}
@ -164,12 +164,7 @@ fun NavDrawer(
LibraryNavItem(
library = it,
onClick = {
navigationManager.navigateTo(
Destination.MediaItem(
it.id,
it.type,
),
)
navigationManager.navigateTo(it.destination())
},
)
}
@ -234,13 +229,13 @@ fun NavigationDrawerScope.IconNavItem(
@Composable
fun NavigationDrawerScope.LibraryNavItem(
library: Library,
library: BaseItem,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
// TODO
val icon =
when (library.collectionType) {
when (library.data.collectionType) {
CollectionType.MOVIES -> R.string.fa_film
CollectionType.TVSHOWS -> R.string.fa_tv
CollectionType.HOMEVIDEOS -> R.string.fa_video

View file

@ -144,6 +144,7 @@ class ApiRequestPager<T>(
private val scope: CoroutineScope,
private val pageSize: Int = DEFAULT_PAGE_SIZE,
cacheSize: Long = 8,
private val useSeriesForPrimary: Boolean = false,
) : AbstractList<BaseItem?>(),
BlockingList<BaseItem?> {
private var items by mutableStateOf(ItemList<BaseItem>(0, pageSize, mapOf()))
@ -217,7 +218,7 @@ class ApiRequestPager<T>(
false,
)
val result = requestHandler.execute(api, newRequest).content
val data = result.items.map { BaseItem.from(it, api) }
val data = result.items.map { BaseItem.from(it, api, useSeriesForPrimary) }
cachedPages.put(pageNumber, data)
items = ItemList(totalCount, pageSize, cachedPages.asMap())
}