mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 16:11:20 +02:00
Improved home page support for Live TV & Recordings (#890)
## Description Improves home page customization for Live TV & Recordings - Add row for live tv channels - Add row for "on now" programs - Clicking on a TV channel or TV program starts playback - Better name for "Recently recorded" instead of "Recently added in Recordings" Also fixes a back stack issue after adding rows ### Related issues Follow up from #803 ### Testing Emulator ## Screenshots <img width="663" height="190" alt="image" src="https://github.com/user-attachments/assets/49d41953-42ab-4621-92f1-b7936aab7e97" /> ## AI or LLM usage None
This commit is contained in:
parent
7fcd66f407
commit
91900af7a9
13 changed files with 229 additions and 64 deletions
|
|
@ -12,13 +12,14 @@ import com.github.damontecres.wholphin.preferences.HomePagePreferences
|
|||
import com.github.damontecres.wholphin.ui.DefaultItemFields
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.components.getGenreImageMap
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.main.settings.Library
|
||||
import com.github.damontecres.wholphin.ui.toBaseItems
|
||||
import com.github.damontecres.wholphin.ui.toServerString
|
||||
import com.github.damontecres.wholphin.util.GetGenresRequestHandler
|
||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||
import com.github.damontecres.wholphin.util.GetPersonsHandler
|
||||
import com.github.damontecres.wholphin.util.HomeRowLoadingState
|
||||
import com.github.damontecres.wholphin.util.HomeRowLoadingState.Error
|
||||
import com.github.damontecres.wholphin.util.HomeRowLoadingState.Success
|
||||
import com.github.damontecres.wholphin.util.supportedHomeCollectionTypes
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
|
|
@ -43,6 +44,7 @@ import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
|||
import org.jellyfin.sdk.api.client.extensions.userViewsApi
|
||||
import org.jellyfin.sdk.model.UUID
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.CollectionType
|
||||
import org.jellyfin.sdk.model.api.ImageType
|
||||
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||
import org.jellyfin.sdk.model.api.SortOrder
|
||||
|
|
@ -255,11 +257,12 @@ class HomeSettingsService
|
|||
suspend fun createDefault(userId: UUID): HomePageResolvedSettings {
|
||||
Timber.v("Creating default settings")
|
||||
val user = serverRepository.currentUser.value?.takeIf { it.id == userId }
|
||||
val userDto = serverRepository.currentUserDto.value?.takeIf { it.id == userId }
|
||||
val libraries =
|
||||
if (user != null) {
|
||||
navDrawerService.getFilteredUserLibraries(user)
|
||||
navDrawerService.getFilteredUserLibraries(user, userDto?.tvAccess ?: false)
|
||||
} else {
|
||||
navDrawerService.getAllUserLibraries(userId)
|
||||
navDrawerService.getAllUserLibraries(userId, userDto?.tvAccess ?: false)
|
||||
}
|
||||
|
||||
val prefs =
|
||||
|
|
@ -269,18 +272,23 @@ class HomeSettingsService
|
|||
libraries
|
||||
.mapIndexed { index, it ->
|
||||
val parentId = it.itemId
|
||||
val name = it.name.takeIf { it.isNotNullOrBlank() }
|
||||
val title =
|
||||
name?.let { context.getString(R.string.recently_added_in, it) }
|
||||
?: context.getString(R.string.recently_added)
|
||||
HomeRowConfigDisplay(
|
||||
id = index,
|
||||
title = title,
|
||||
config = HomeRowConfig.RecentlyAdded(parentId),
|
||||
)
|
||||
val title = getRecentlyAddedTitle(context, it)
|
||||
if (it.collectionType == CollectionType.LIVETV) {
|
||||
HomeRowConfigDisplay(
|
||||
id = index,
|
||||
title = context.getString(R.string.live_tv),
|
||||
config = HomeRowConfig.TvPrograms(),
|
||||
)
|
||||
} else {
|
||||
HomeRowConfigDisplay(
|
||||
id = index,
|
||||
title = title,
|
||||
config = HomeRowConfig.RecentlyAdded(parentId),
|
||||
)
|
||||
}
|
||||
}
|
||||
val continueWatchingRows =
|
||||
if (prefs.combineContinueNext) { // TODO
|
||||
if (prefs.combineContinueNext) {
|
||||
listOf(
|
||||
HomeRowConfigDisplay(
|
||||
id = includedIds.size + 1,
|
||||
|
|
@ -363,7 +371,7 @@ class HomeSettingsService
|
|||
}
|
||||
|
||||
HomeSectionType.LIVE_TV -> {
|
||||
if (userDto.policy?.enableLiveTvAccess == true) {
|
||||
if (userDto.tvAccess) {
|
||||
HomeRowConfigDisplay(
|
||||
id = id++,
|
||||
title = context.getString(R.string.live_tv),
|
||||
|
|
@ -523,6 +531,14 @@ class HomeSettingsService
|
|||
)
|
||||
}
|
||||
|
||||
is HomeRowConfig.TvChannels -> {
|
||||
HomeRowConfigDisplay(
|
||||
id = id,
|
||||
title = context.getString(R.string.channels),
|
||||
config,
|
||||
)
|
||||
}
|
||||
|
||||
is HomeRowConfig.Suggestions -> {
|
||||
val name =
|
||||
api.userLibraryApi
|
||||
|
|
@ -654,13 +670,10 @@ class HomeSettingsService
|
|||
}
|
||||
|
||||
is HomeRowConfig.RecentlyAdded -> {
|
||||
val name =
|
||||
val library =
|
||||
libraries
|
||||
.firstOrNull { it.itemId == row.parentId }
|
||||
?.name
|
||||
val title =
|
||||
name?.let { context.getString(R.string.recently_added_in, it) }
|
||||
?: context.getString(R.string.recently_added)
|
||||
val title = getRecentlyAddedTitle(context, library)
|
||||
val request =
|
||||
GetLatestMediaRequest(
|
||||
fields = SlimItemFields,
|
||||
|
|
@ -862,6 +875,23 @@ class HomeSettingsService
|
|||
}
|
||||
}
|
||||
|
||||
is HomeRowConfig.TvChannels -> {
|
||||
api.liveTvApi
|
||||
.getLiveTvChannels(
|
||||
userId = userDto.id,
|
||||
fields = DefaultItemFields,
|
||||
limit = limit,
|
||||
enableImages = true,
|
||||
).toBaseItems(api, row.viewOptions.useSeries)
|
||||
.let {
|
||||
Success(
|
||||
context.getString(R.string.channels),
|
||||
it,
|
||||
row.viewOptions,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is HomeRowConfig.Suggestions -> {
|
||||
val library =
|
||||
api.userLibraryApi
|
||||
|
|
@ -888,7 +918,7 @@ class HomeSettingsService
|
|||
row.viewOptions,
|
||||
)
|
||||
} else {
|
||||
HomeRowLoadingState.Error(
|
||||
Error(
|
||||
title,
|
||||
message = "Unsupported type ${library.collectionType}",
|
||||
)
|
||||
|
|
@ -949,3 +979,14 @@ class UnsupportedHomeSettingsVersionException(
|
|||
val unsupportedVersion: Int?,
|
||||
val maxSupportedVersion: Int = SUPPORTED_HOME_PAGE_SETTINGS_VERSION,
|
||||
) : Exception("Unsupported version $unsupportedVersion, max supported is $maxSupportedVersion")
|
||||
|
||||
fun getRecentlyAddedTitle(
|
||||
context: Context,
|
||||
library: Library?,
|
||||
): String =
|
||||
if (library?.isRecordingFolder == true) {
|
||||
context.getString(R.string.recently_recorded)
|
||||
} else {
|
||||
library?.name?.let { context.getString(R.string.recently_added_in, it) }
|
||||
?: context.getString(R.string.recently_added)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import android.content.Context
|
|||
import androidx.lifecycle.asFlow
|
||||
import com.github.damontecres.wholphin.data.ServerPreferencesDao
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||
import com.github.damontecres.wholphin.data.model.NavPinType
|
||||
import com.github.damontecres.wholphin.services.hilt.DefaultCoroutineScope
|
||||
|
|
@ -68,25 +67,49 @@ class NavDrawerService
|
|||
}.launchIn(coroutineScope)
|
||||
}
|
||||
|
||||
suspend fun getAllUserLibraries(userId: UUID): List<Library> {
|
||||
suspend fun getAllUserLibraries(
|
||||
userId: UUID,
|
||||
tvAccess: Boolean,
|
||||
): List<Library> {
|
||||
val userViews =
|
||||
api.userViewsApi
|
||||
.getUserViews(userId = userId)
|
||||
.content.items
|
||||
val recordingFolders =
|
||||
if (tvAccess) {
|
||||
api.liveTvApi
|
||||
.getRecordingFolders(userId = userId)
|
||||
.content.items
|
||||
.map { it.id }
|
||||
.toSet()
|
||||
} else {
|
||||
setOf()
|
||||
}
|
||||
val libraries =
|
||||
userViews
|
||||
.filter { it.collectionType in supportedCollectionTypes }
|
||||
.map { Library(it.id, it.name ?: "", it.collectionType ?: CollectionType.UNKNOWN) }
|
||||
.filter { it.collectionType in supportedCollectionTypes || it.id in recordingFolders }
|
||||
.map {
|
||||
Library(
|
||||
itemId = it.id,
|
||||
name = it.name ?: "",
|
||||
type = it.type,
|
||||
collectionType = it.collectionType ?: CollectionType.UNKNOWN,
|
||||
isRecordingFolder = it.id in recordingFolders,
|
||||
)
|
||||
}
|
||||
return libraries
|
||||
}
|
||||
|
||||
suspend fun getFilteredUserLibraries(user: JellyfinUser): List<Library> {
|
||||
suspend fun getFilteredUserLibraries(
|
||||
user: JellyfinUser,
|
||||
tvAccess: Boolean,
|
||||
): List<Library> {
|
||||
val pins =
|
||||
serverPreferencesDao
|
||||
.getNavDrawerPinnedItems(user)
|
||||
.associateBy { it.itemId }
|
||||
val libraries =
|
||||
getAllUserLibraries(user.id)
|
||||
getAllUserLibraries(user.id, tvAccess)
|
||||
.filterNot { pins[ServerNavDrawerItem.getId(it.itemId)]?.type == NavPinType.UNPINNED }
|
||||
return libraries
|
||||
}
|
||||
|
|
@ -95,39 +118,26 @@ class NavDrawerService
|
|||
user: JellyfinUser,
|
||||
userDto: UserDto,
|
||||
) {
|
||||
val tvAccess = userDto.policy?.enableLiveTvAccess ?: false
|
||||
val userViews =
|
||||
api.userViewsApi
|
||||
.getUserViews(userId = user.id)
|
||||
.content.items
|
||||
val recordingFolders =
|
||||
if (tvAccess) {
|
||||
api.liveTvApi
|
||||
.getRecordingFolders(userId = user.id)
|
||||
.content.items
|
||||
.map { it.id }
|
||||
.toSet()
|
||||
} else {
|
||||
setOf()
|
||||
}
|
||||
|
||||
val builtins = listOf(NavDrawerItem.Favorites, NavDrawerItem.Discover)
|
||||
|
||||
val allLibraries = getAllUserLibraries(user.id, userDto.tvAccess)
|
||||
val libraries =
|
||||
userViews
|
||||
.filter { it.collectionType in supportedCollectionTypes || it.id in recordingFolders }
|
||||
allLibraries
|
||||
.map {
|
||||
val destination =
|
||||
if (it.id in recordingFolders) {
|
||||
Destination.Recordings(it.id)
|
||||
if (it.isRecordingFolder) {
|
||||
Destination.Recordings(it.itemId)
|
||||
} else {
|
||||
BaseItem.from(it, api).destination()
|
||||
Destination.MediaItem(
|
||||
it.itemId,
|
||||
it.type,
|
||||
it.collectionType,
|
||||
)
|
||||
}
|
||||
ServerNavDrawerItem(
|
||||
itemId = it.id,
|
||||
name = it.name ?: it.id.toString(),
|
||||
itemId = it.itemId,
|
||||
name = it.name,
|
||||
destination = destination,
|
||||
type = it.collectionType ?: CollectionType.UNKNOWN,
|
||||
type = it.collectionType,
|
||||
)
|
||||
}
|
||||
val allItems = builtins + libraries
|
||||
|
|
@ -170,3 +180,5 @@ data class NavDrawerItemState(
|
|||
val EMPTY = NavDrawerItemState(emptyList(), emptyList(), false)
|
||||
}
|
||||
}
|
||||
|
||||
val UserDto.tvAccess: Boolean get() = policy?.enableLiveTvAccess == true
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue