mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Merge branch 'main' into develop/mpv
This commit is contained in:
commit
91ad74615b
64 changed files with 750 additions and 387 deletions
|
|
@ -25,14 +25,17 @@ After forking and cloning your fork, you can import the project into Android Stu
|
|||
|
||||
You need a compatible Android Studio version for the configured AGP. This is generally `Narwhal 3 Feature Drop | 2025.1.3` or newer. See https://developer.android.com/build/releases/gradle-plugin and [`libs.versions.toml](./gradle/libs.versions.toml).
|
||||
|
||||
### Code organization
|
||||
|
||||
Code is split into several packages:
|
||||
- `data` - app-specific data models and services
|
||||
- `preferences` - Non-UI related code for user settings and preferences
|
||||
- `services` - hilt injectable services often used by ViewModels for API calls
|
||||
- `ui` - User interface code and ViewModels
|
||||
- `util` - Utility classes and functions
|
||||
|
||||
### Native components
|
||||
|
||||
Wholphin includes some optional native components that are compiled outside of the normal Android gradle build process.
|
||||
|
||||
These components are not generally required to build or test the app during development.
|
||||
|
||||
If you want to build any of them locally, you must have the [Android NDK](https://developer.android.com/ndk) installed.
|
||||
|
||||
#### FFmpeg decoder module
|
||||
|
||||
Wholphin ships with [media3 ffmpeg decoder module](https://github.com/androidx/media/blob/release/libraries/decoder_ffmpeg/README.md).
|
||||
|
|
|
|||
|
|
@ -27,20 +27,20 @@ import androidx.tv.material3.ExperimentalTvMaterial3Api
|
|||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.Surface
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.hilt.AuthOkHttpClient
|
||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||
import com.github.damontecres.wholphin.preferences.DefaultUserConfiguration
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.AppUpgradeHandler
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.PlaybackLifecycleObserver
|
||||
import com.github.damontecres.wholphin.services.ServerEventListener
|
||||
import com.github.damontecres.wholphin.services.UpdateChecker
|
||||
import com.github.damontecres.wholphin.services.hilt.AuthOkHttpClient
|
||||
import com.github.damontecres.wholphin.ui.CoilConfig
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.nav.ApplicationContent
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||
import com.github.damontecres.wholphin.util.AppUpgradeHandler
|
||||
import com.github.damontecres.wholphin.util.PlaybackLifecycleObserver
|
||||
import com.github.damontecres.wholphin.util.ServerEventListener
|
||||
import com.github.damontecres.wholphin.util.UpdateChecker
|
||||
import com.github.damontecres.wholphin.util.profile.createDeviceProfile
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import okhttp3.OkHttpClient
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.github.damontecres.wholphin.data.model
|
|||
|
||||
import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisodeIds
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.util.seasonEpisode
|
||||
import com.github.damontecres.wholphin.ui.seasonEpisode
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import kotlinx.serialization.Serializable
|
|||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetPersonsRequest
|
||||
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
||||
import java.util.UUID
|
||||
|
||||
|
|
@ -19,6 +20,7 @@ data class GetItemsFilter(
|
|||
val studios: List<UUID>? = null,
|
||||
val tags: List<String>? = null,
|
||||
val includeItemTypes: List<BaseItemKind>? = null,
|
||||
val override: GetItemsFilterOverride = GetItemsFilterOverride.NONE,
|
||||
) {
|
||||
fun applyTo(req: GetItemsRequest) =
|
||||
req.copy(
|
||||
|
|
@ -31,4 +33,14 @@ data class GetItemsFilter(
|
|||
studioIds = studios,
|
||||
tags = tags,
|
||||
)
|
||||
|
||||
fun applyTo(req: GetPersonsRequest) =
|
||||
req.copy(
|
||||
isFavorite = favorite,
|
||||
)
|
||||
}
|
||||
|
||||
enum class GetItemsFilterOverride {
|
||||
NONE,
|
||||
PERSON,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ data class Person(
|
|||
val role: String?,
|
||||
val type: PersonKind,
|
||||
val imageUrl: String?,
|
||||
val favorite: Boolean,
|
||||
) {
|
||||
companion object {
|
||||
fun fromDto(
|
||||
|
|
@ -25,6 +26,21 @@ data class Person(
|
|||
role = dto.role,
|
||||
type = dto.type,
|
||||
imageUrl = api.imageApi.getItemImageUrl(dto.id, ImageType.PRIMARY),
|
||||
favorite = false,
|
||||
)
|
||||
|
||||
fun fromDto(
|
||||
dto: BaseItemPerson,
|
||||
favorite: Boolean,
|
||||
api: ApiClient,
|
||||
): Person =
|
||||
Person(
|
||||
id = dto.id,
|
||||
name = dto.name,
|
||||
role = dto.role,
|
||||
type = dto.type,
|
||||
imageUrl = api.imageApi.getItemImageUrl(dto.id, ImageType.PRIMARY),
|
||||
favorite = favorite,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +1,10 @@
|
|||
package com.github.damontecres.wholphin.data.model
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.ui.DefaultItemFields
|
||||
import com.github.damontecres.wholphin.ui.indexOfFirstOrNull
|
||||
import com.github.damontecres.wholphin.ui.toServerString
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.GetEpisodesRequestHandler
|
||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||
import com.github.damontecres.wholphin.util.GetPlaylistItemsRequestHandler
|
||||
import com.github.damontecres.wholphin.util.TransformList
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.playlistsApi
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.CreatePlaylistDto
|
||||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import org.jellyfin.sdk.model.api.PlaylistUserPermissions
|
||||
import org.jellyfin.sdk.model.api.request.GetEpisodesRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetPlaylistItemsRequest
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
class Playlist(
|
||||
items: List<BaseItem>,
|
||||
|
|
@ -71,107 +47,3 @@ data class PlaylistInfo(
|
|||
val count: Int,
|
||||
val mediaType: MediaType,
|
||||
)
|
||||
|
||||
@Singleton
|
||||
class PlaylistCreator
|
||||
@Inject
|
||||
constructor(
|
||||
@param:ApplicationContext private val context: Context,
|
||||
private val api: ApiClient,
|
||||
private val serverRepository: ServerRepository,
|
||||
) {
|
||||
suspend fun createFromEpisode(
|
||||
seriesId: UUID,
|
||||
episodeId: UUID,
|
||||
): Playlist {
|
||||
val request =
|
||||
GetEpisodesRequest(
|
||||
seriesId = seriesId,
|
||||
fields = DefaultItemFields,
|
||||
startItemId = episodeId,
|
||||
limit = Playlist.MAX_SIZE,
|
||||
)
|
||||
val episodes = GetEpisodesRequestHandler.execute(api, request).content.items
|
||||
val startIndex =
|
||||
episodes.indexOfFirstOrNull { it.id == episodeId }
|
||||
?: throw IllegalStateException("Episode $episodeId was not returned")
|
||||
return Playlist(episodes.map { BaseItem.from(it, api) }, startIndex)
|
||||
}
|
||||
|
||||
suspend fun createFromPlaylistId(
|
||||
playlistId: UUID,
|
||||
startIndex: Int?,
|
||||
shuffled: Boolean,
|
||||
): Playlist {
|
||||
val request =
|
||||
GetPlaylistItemsRequest(
|
||||
playlistId = playlistId,
|
||||
fields = DefaultItemFields,
|
||||
startIndex = startIndex,
|
||||
limit = Playlist.MAX_SIZE,
|
||||
)
|
||||
val items = GetPlaylistItemsRequestHandler.execute(api, request).content.items
|
||||
var baseItems = items.map { BaseItem.from(it, api) }
|
||||
if (shuffled) {
|
||||
baseItems = baseItems.shuffled()
|
||||
}
|
||||
return Playlist(baseItems, 0)
|
||||
}
|
||||
|
||||
suspend fun getPlaylists(
|
||||
mediaType: MediaType?,
|
||||
scope: CoroutineScope,
|
||||
): List<PlaylistInfo?> {
|
||||
val request =
|
||||
GetItemsRequest(
|
||||
includeItemTypes = listOf(BaseItemKind.PLAYLIST),
|
||||
mediaTypes = mediaType?.let { listOf(mediaType) },
|
||||
recursive = true,
|
||||
)
|
||||
val pager = ApiRequestPager(api, request, GetItemsRequestHandler, scope).init()
|
||||
return TransformList(pager) {
|
||||
it?.let {
|
||||
PlaylistInfo(
|
||||
id = it.id,
|
||||
name = it.name ?: context.getString(R.string.unknown),
|
||||
count = it.data.childCount ?: 0,
|
||||
mediaType = it.data.mediaType,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createPlaylist(
|
||||
name: String,
|
||||
initialItems: List<UUID>,
|
||||
): UUID? =
|
||||
serverRepository.currentUser.value?.let { user ->
|
||||
api.playlistsApi
|
||||
.createPlaylist(
|
||||
CreatePlaylistDto(
|
||||
name = name,
|
||||
ids = initialItems,
|
||||
users = listOf(PlaylistUserPermissions(user.id, true)),
|
||||
isPublic = false,
|
||||
),
|
||||
).content.id
|
||||
.toUUIDOrNull()
|
||||
}
|
||||
|
||||
suspend fun addToPlaylist(
|
||||
playlistId: UUID,
|
||||
itemId: UUID,
|
||||
) {
|
||||
api.playlistsApi.addItemToPlaylist(playlistId, listOf(itemId))
|
||||
}
|
||||
|
||||
suspend fun removeFromPlaylist(
|
||||
playlistId: UUID,
|
||||
itemId: UUID,
|
||||
) {
|
||||
api.playlistsApi.removeItemFromPlaylist(
|
||||
playlistId.toServerString(),
|
||||
listOf(itemId.toServerString()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.github.damontecres.wholphin.util
|
||||
package com.github.damontecres.wholphin.services
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
|
|
@ -11,6 +11,7 @@ import com.github.damontecres.wholphin.preferences.AppPreferences
|
|||
import com.github.damontecres.wholphin.preferences.updateInterfacePreferences
|
||||
import com.github.damontecres.wholphin.preferences.updatePlaybackOverrides
|
||||
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
|
||||
import com.github.damontecres.wholphin.util.Version
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
|
|
@ -51,8 +52,8 @@ class AppUpgradeHandler
|
|||
copySubfont(true)
|
||||
upgradeApp(
|
||||
context,
|
||||
Version.fromString(previousVersion ?: "0.0.0"),
|
||||
Version.fromString(newVersion),
|
||||
Version.Companion.fromString(previousVersion ?: "0.0.0"),
|
||||
Version.Companion.fromString(newVersion),
|
||||
appPreferences,
|
||||
)
|
||||
} catch (ex: Exception) {
|
||||
|
|
@ -96,7 +97,7 @@ suspend fun upgradeApp(
|
|||
current: Version,
|
||||
appPreferences: DataStore<AppPreferences>,
|
||||
) {
|
||||
if (previous.isEqualOrBefore(Version.fromString("0.1.0-2-g0"))) {
|
||||
if (previous.isEqualOrBefore(Version.Companion.fromString("0.1.0-2-g0"))) {
|
||||
appPreferences.updateData {
|
||||
it.updatePlaybackOverrides {
|
||||
ac3Supported = AppPreference.Ac3Supported.defaultValue
|
||||
|
|
@ -106,21 +107,21 @@ suspend fun upgradeApp(
|
|||
}
|
||||
}
|
||||
}
|
||||
if (previous.isEqualOrBefore(Version.fromString("0.2.3-6-g0"))) {
|
||||
if (previous.isEqualOrBefore(Version.Companion.fromString("0.2.3-6-g0"))) {
|
||||
appPreferences.updateData {
|
||||
it.updateInterfacePreferences {
|
||||
navDrawerSwitchOnFocus = AppPreference.NavDrawerSwitchOnFocus.defaultValue
|
||||
}
|
||||
}
|
||||
}
|
||||
if (previous.isEqualOrBefore(Version.fromString("0.2.5-11-g0"))) {
|
||||
if (previous.isEqualOrBefore(Version.Companion.fromString("0.2.5-11-g0"))) {
|
||||
appPreferences.updateData {
|
||||
it.updateInterfacePreferences {
|
||||
showClock = AppPreference.ShowClock.defaultValue
|
||||
}
|
||||
}
|
||||
}
|
||||
if (previous.isEqualOrBefore(Version.fromString("0.2.7-1-g0"))) {
|
||||
if (previous.isEqualOrBefore(Version.Companion.fromString("0.2.7-1-g0"))) {
|
||||
PreferencesViewModel.resetSubtitleSettings(appPreferences)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.github.damontecres.wholphin.util
|
||||
package com.github.damontecres.wholphin.services
|
||||
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.playStateApi
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.github.damontecres.wholphin.ui.nav
|
||||
package com.github.damontecres.wholphin.services
|
||||
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import org.acra.ACRA
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
|
@ -16,7 +17,7 @@ class NavigationManager
|
|||
var backStack: MutableList<NavKey> = mutableListOf()
|
||||
|
||||
/**
|
||||
* Go to the specified [Destination]
|
||||
* Go to the specified [com.github.damontecres.wholphin.ui.nav.Destination]
|
||||
*/
|
||||
fun navigateTo(destination: Destination) {
|
||||
backStack.add(destination)
|
||||
|
|
@ -70,7 +71,7 @@ class NavigationManager
|
|||
|
||||
private fun log() {
|
||||
val dest = backStack.lastOrNull().toString()
|
||||
Timber.i("Current Destination: %s", dest)
|
||||
Timber.Forest.i("Current Destination: %s", dest)
|
||||
ACRA.errorReporter.putCustomData("destination", dest)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.github.damontecres.wholphin.services
|
||||
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.Person
|
||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.itemsApi
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class PeopleFavorites
|
||||
@Inject
|
||||
constructor(
|
||||
private val api: ApiClient,
|
||||
) {
|
||||
suspend fun getPeopleFor(item: BaseItem): List<Person> =
|
||||
item.data.people.orEmpty().map { it.id }.chunked(50).let { chunks ->
|
||||
val favorites =
|
||||
chunks
|
||||
.map {
|
||||
api.itemsApi
|
||||
.getItems(ids = it)
|
||||
.content.items
|
||||
}.flatten()
|
||||
.associateBy({ it.id }, { it.userData?.isFavorite ?: false })
|
||||
val people =
|
||||
item.data.people
|
||||
?.letNotEmpty { people ->
|
||||
people.map { Person.fromDto(it, favorites[it.id] ?: false, api) }
|
||||
}.orEmpty()
|
||||
people
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,8 @@
|
|||
package com.github.damontecres.wholphin.util
|
||||
package com.github.damontecres.wholphin.services
|
||||
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import dagger.hilt.android.scopes.ActivityRetainedScoped
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
@file:OptIn(markerClass = [UnstableApi::class])
|
||||
|
||||
package com.github.damontecres.wholphin.util
|
||||
package com.github.damontecres.wholphin.services
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.OptIn
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
package com.github.damontecres.wholphin.services
|
||||
|
||||
import android.content.Context
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.Playlist
|
||||
import com.github.damontecres.wholphin.data.model.PlaylistInfo
|
||||
import com.github.damontecres.wholphin.ui.DefaultItemFields
|
||||
import com.github.damontecres.wholphin.ui.indexOfFirstOrNull
|
||||
import com.github.damontecres.wholphin.ui.toServerString
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.GetEpisodesRequestHandler
|
||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||
import com.github.damontecres.wholphin.util.GetPlaylistItemsRequestHandler
|
||||
import com.github.damontecres.wholphin.util.TransformList
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.playlistsApi
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.CreatePlaylistDto
|
||||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import org.jellyfin.sdk.model.api.PlaylistUserPermissions
|
||||
import org.jellyfin.sdk.model.api.request.GetEpisodesRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetPlaylistItemsRequest
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class PlaylistCreator
|
||||
@Inject
|
||||
constructor(
|
||||
@param:ApplicationContext private val context: Context,
|
||||
private val api: ApiClient,
|
||||
private val serverRepository: ServerRepository,
|
||||
) {
|
||||
suspend fun createFromEpisode(
|
||||
seriesId: UUID,
|
||||
episodeId: UUID,
|
||||
): Playlist {
|
||||
val request =
|
||||
GetEpisodesRequest(
|
||||
seriesId = seriesId,
|
||||
fields = DefaultItemFields,
|
||||
startItemId = episodeId,
|
||||
limit = Playlist.Companion.MAX_SIZE,
|
||||
)
|
||||
val episodes = GetEpisodesRequestHandler.execute(api, request).content.items
|
||||
val startIndex =
|
||||
episodes.indexOfFirstOrNull { it.id == episodeId }
|
||||
?: throw IllegalStateException("Episode $episodeId was not returned")
|
||||
return Playlist(episodes.map { BaseItem.Companion.from(it, api) }, startIndex)
|
||||
}
|
||||
|
||||
suspend fun createFromPlaylistId(
|
||||
playlistId: UUID,
|
||||
startIndex: Int?,
|
||||
shuffled: Boolean,
|
||||
): Playlist {
|
||||
val request =
|
||||
GetPlaylistItemsRequest(
|
||||
playlistId = playlistId,
|
||||
fields = DefaultItemFields,
|
||||
startIndex = startIndex,
|
||||
limit = Playlist.Companion.MAX_SIZE,
|
||||
)
|
||||
val items = GetPlaylistItemsRequestHandler.execute(api, request).content.items
|
||||
var baseItems = items.map { BaseItem.Companion.from(it, api) }
|
||||
if (shuffled) {
|
||||
baseItems = baseItems.shuffled()
|
||||
}
|
||||
return Playlist(baseItems, 0)
|
||||
}
|
||||
|
||||
suspend fun getPlaylists(
|
||||
mediaType: MediaType?,
|
||||
scope: CoroutineScope,
|
||||
): List<PlaylistInfo?> {
|
||||
val request =
|
||||
GetItemsRequest(
|
||||
includeItemTypes = listOf(BaseItemKind.PLAYLIST),
|
||||
mediaTypes = mediaType?.let { listOf(mediaType) },
|
||||
recursive = true,
|
||||
)
|
||||
val pager = ApiRequestPager(api, request, GetItemsRequestHandler, scope).init()
|
||||
return TransformList(pager) {
|
||||
it?.let {
|
||||
PlaylistInfo(
|
||||
id = it.id,
|
||||
name = it.name ?: context.getString(R.string.unknown),
|
||||
count = it.data.childCount ?: 0,
|
||||
mediaType = it.data.mediaType,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun createPlaylist(
|
||||
name: String,
|
||||
initialItems: List<UUID>,
|
||||
): UUID? =
|
||||
serverRepository.currentUser.value?.let { user ->
|
||||
api.playlistsApi
|
||||
.createPlaylist(
|
||||
CreatePlaylistDto(
|
||||
name = name,
|
||||
ids = initialItems,
|
||||
users = listOf(PlaylistUserPermissions(user.id, true)),
|
||||
isPublic = false,
|
||||
),
|
||||
).content.id
|
||||
.toUUIDOrNull()
|
||||
}
|
||||
|
||||
suspend fun addToPlaylist(
|
||||
playlistId: UUID,
|
||||
itemId: UUID,
|
||||
) {
|
||||
api.playlistsApi.addItemToPlaylist(playlistId, listOf(itemId))
|
||||
}
|
||||
|
||||
suspend fun removeFromPlaylist(
|
||||
playlistId: UUID,
|
||||
itemId: UUID,
|
||||
) {
|
||||
api.playlistsApi.removeItemFromPlaylist(
|
||||
playlistId.toServerString(),
|
||||
listOf(itemId.toServerString()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.github.damontecres.wholphin.util
|
||||
package com.github.damontecres.wholphin.services
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.github.damontecres.wholphin.util
|
||||
package com.github.damontecres.wholphin.services
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.OptIn
|
||||
|
|
@ -8,8 +8,8 @@ import androidx.media3.common.util.UnstableApi
|
|||
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||
import com.github.damontecres.wholphin.hilt.AuthOkHttpClient
|
||||
import com.github.damontecres.wholphin.preferences.ThemeSongVolume
|
||||
import com.github.damontecres.wholphin.services.hilt.AuthOkHttpClient
|
||||
import com.github.damontecres.wholphin.util.profile.Codec
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
package com.github.damontecres.wholphin.services
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.net.toUri
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.LocalTrailer
|
||||
import com.github.damontecres.wholphin.data.model.RemoteTrailer
|
||||
import com.github.damontecres.wholphin.data.model.Trailer
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class TrailerService
|
||||
@Inject
|
||||
constructor(
|
||||
@param:ApplicationContext private val context: Context,
|
||||
private val api: ApiClient,
|
||||
) {
|
||||
suspend fun getTrailers(item: BaseItem): List<Trailer> {
|
||||
val remoteTrailers =
|
||||
item.data.remoteTrailers
|
||||
?.mapNotNull { t ->
|
||||
t.url?.let { url ->
|
||||
val name =
|
||||
t.name
|
||||
// TODO would be nice to clean up the trailer name
|
||||
// ?.replace(item.name ?: "", "")
|
||||
// ?.removePrefix(" - ")
|
||||
?: context.getString(R.string.trailer)
|
||||
RemoteTrailer(name, url)
|
||||
}
|
||||
}.orEmpty()
|
||||
.sortedBy { it.name }
|
||||
val localTrailerCount = item.data.localTrailerCount ?: 0
|
||||
val localTrailers =
|
||||
if (localTrailerCount > 0) {
|
||||
api.userLibraryApi.getLocalTrailers(item.id).content.map {
|
||||
LocalTrailer(BaseItem.Companion.from(it, api))
|
||||
}
|
||||
} else {
|
||||
listOf()
|
||||
}
|
||||
return localTrailers + remoteTrailers
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Note: This is explicitly <b>not</b> a member function because the injected Context is not an Activity.
|
||||
* We want to start the intent without a new task which requires the Activity context
|
||||
*
|
||||
* This can be provided by LocalContext.current from Compose
|
||||
*/
|
||||
fun onClick(
|
||||
context: Context,
|
||||
trailer: Trailer,
|
||||
navigateTo: (Destination) -> Unit,
|
||||
) {
|
||||
when (trailer) {
|
||||
is LocalTrailer ->
|
||||
navigateTo.invoke(
|
||||
Destination.Playback(
|
||||
itemId = trailer.baseItem.id,
|
||||
item = trailer.baseItem,
|
||||
positionMs = 0L,
|
||||
),
|
||||
)
|
||||
|
||||
is RemoteTrailer -> {
|
||||
val intent = Intent(Intent.ACTION_VIEW, trailer.url.toUri())
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.github.damontecres.wholphin.util
|
||||
package com.github.damontecres.wholphin.services
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ContentValues
|
||||
|
|
@ -15,9 +15,10 @@ import androidx.core.content.FileProvider
|
|||
import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.hilt.StandardOkHttpClient
|
||||
import com.github.damontecres.wholphin.services.hilt.StandardOkHttpClient
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.showToast
|
||||
import com.github.damontecres.wholphin.util.Version
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -110,7 +111,7 @@ class UpdateChecker
|
|||
|
||||
fun getInstalledVersion(): Version {
|
||||
val pkgInfo = context.packageManager.getPackageInfo(context.packageName, 0)
|
||||
return Version.fromString(pkgInfo.versionName!!)
|
||||
return Version.Companion.fromString(pkgInfo.versionName!!)
|
||||
}
|
||||
|
||||
suspend fun getLatestRelease(updateUrl: String): Release? {
|
||||
|
|
@ -135,7 +136,7 @@ class UpdateChecker
|
|||
if (it.isSuccessful && it.body != null) {
|
||||
val result = Json.parseToJsonElement(it.body!!.string())
|
||||
val name = result.jsonObject["name"]?.jsonPrimitive?.contentOrNull
|
||||
val version = Version.tryFromString(name)
|
||||
val version = Version.Companion.tryFromString(name)
|
||||
val publishedAt =
|
||||
result.jsonObject["published_at"]?.jsonPrimitive?.contentOrNull
|
||||
val body = result.jsonObject["body"]?.jsonPrimitive?.contentOrNull
|
||||
|
|
@ -236,7 +237,7 @@ class UpdateChecker
|
|||
val targetFile = fallbackDownload(it, callback)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
val intent = Intent(Intent.ACTION_INSTALL_PACKAGE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
intent.data =
|
||||
FileProvider.getUriForFile(
|
||||
context,
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.github.damontecres.wholphin.hilt
|
||||
package com.github.damontecres.wholphin.services.hilt
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
|
|
@ -10,9 +10,9 @@ import com.github.damontecres.wholphin.data.ServerRepository
|
|||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.preferences.updateInterfacePreferences
|
||||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.services.FavoriteWatchManagerImpl
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.util.FavoriteWatchManagerImpl
|
||||
import com.github.damontecres.wholphin.util.RememberTabManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.github.damontecres.wholphin.hilt
|
||||
package com.github.damontecres.wholphin.services.hilt
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.github.damontecres.wholphin.util
|
||||
package com.github.damontecres.wholphin.ui
|
||||
|
||||
import android.os.Build
|
||||
import androidx.annotation.StringRes
|
||||
|
|
@ -10,7 +10,7 @@ import com.github.damontecres.wholphin.data.model.ItemPlayback
|
|||
import com.github.damontecres.wholphin.data.model.chooseSource
|
||||
import com.github.damontecres.wholphin.data.model.chooseStream
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.util.languageName
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import org.jellyfin.sdk.model.api.MediaSegmentType
|
||||
import org.jellyfin.sdk.model.api.MediaStream
|
||||
|
|
@ -31,7 +31,7 @@ import androidx.tv.material3.Text
|
|||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.ui.AppColors
|
||||
import com.github.damontecres.wholphin.ui.enableMarquee
|
||||
import com.github.damontecres.wholphin.util.seasonEpisode
|
||||
import com.github.damontecres.wholphin.ui.seasonEpisode
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@Composable
|
||||
|
|
@ -105,7 +105,10 @@ fun EpisodeCard(
|
|||
modifier =
|
||||
Modifier
|
||||
.align(Alignment.TopEnd)
|
||||
.background(AppColors.TransparentBlack50, shape = RoundedCornerShape(8.dp)),
|
||||
.background(
|
||||
AppColors.TransparentBlack50,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
),
|
||||
) {
|
||||
Text(
|
||||
text = dto?.seasonEpisode ?: "",
|
||||
|
|
@ -116,7 +119,10 @@ fun EpisodeCard(
|
|||
}
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(0.dp),
|
||||
modifier = Modifier.padding(bottom = spaceBelow).fillMaxWidth(),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(bottom = spaceBelow)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Text(
|
||||
text = dto?.seriesName ?: "",
|
||||
|
|
|
|||
|
|
@ -72,8 +72,8 @@ fun PersonCard(
|
|||
ItemCardImage(
|
||||
imageUrl = item.imageUrl,
|
||||
name = item.name,
|
||||
showOverlay = false,
|
||||
favorite = false,
|
||||
showOverlay = true,
|
||||
favorite = item.favorite,
|
||||
watched = false,
|
||||
unwatchedCount = -1,
|
||||
watchedPercent = null,
|
||||
|
|
|
|||
|
|
@ -34,8 +34,11 @@ import com.github.damontecres.wholphin.data.LibraryDisplayInfoDao
|
|||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||
import com.github.damontecres.wholphin.data.model.GetItemsFilterOverride
|
||||
import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||
|
|
@ -46,13 +49,12 @@ import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
|
|||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.toServerString
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||
import com.github.damontecres.wholphin.util.GetPersonsHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
|
@ -69,6 +71,7 @@ import org.jellyfin.sdk.model.api.ItemSortBy
|
|||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import org.jellyfin.sdk.model.api.SortOrder
|
||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetPersonsRequest
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
|
@ -107,13 +110,12 @@ class CollectionFolderViewModel
|
|||
}
|
||||
|
||||
val sortAndDirection =
|
||||
if (initialSortAndDirection == null) {
|
||||
serverRepository.currentUser.value?.let { user ->
|
||||
libraryDisplayInfoDao.getItem(user, itemId)?.sortAndDirection
|
||||
} ?: SortAndDirection.DEFAULT
|
||||
} else {
|
||||
SortAndDirection.DEFAULT
|
||||
}
|
||||
initialSortAndDirection
|
||||
?: (
|
||||
serverRepository.currentUser.value?.let { user ->
|
||||
libraryDisplayInfoDao.getItem(user, itemId)?.sortAndDirection
|
||||
} ?: SortAndDirection.DEFAULT
|
||||
)
|
||||
|
||||
loadResults(sortAndDirection, recursive, filter)
|
||||
}
|
||||
|
|
@ -143,7 +145,6 @@ class CollectionFolderViewModel
|
|||
recursive: Boolean,
|
||||
filter: GetItemsFilter,
|
||||
) {
|
||||
val item = item.value
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
withContext(Dispatchers.Main) {
|
||||
pager.value = listOf()
|
||||
|
|
@ -151,62 +152,7 @@ class CollectionFolderViewModel
|
|||
this@CollectionFolderViewModel.sortAndDirection.value = sortAndDirection
|
||||
this@CollectionFolderViewModel.filter.value = filter
|
||||
}
|
||||
val includeItemTypes =
|
||||
when (item?.data?.collectionType) {
|
||||
CollectionType.MOVIES -> listOf(BaseItemKind.MOVIE)
|
||||
CollectionType.TVSHOWS -> listOf(BaseItemKind.SERIES)
|
||||
CollectionType.HOMEVIDEOS -> listOf(BaseItemKind.VIDEO)
|
||||
CollectionType.MUSIC ->
|
||||
listOf(
|
||||
BaseItemKind.AUDIO,
|
||||
BaseItemKind.MUSIC_ARTIST,
|
||||
BaseItemKind.MUSIC_ALBUM,
|
||||
)
|
||||
|
||||
CollectionType.BOXSETS -> listOf(BaseItemKind.BOX_SET)
|
||||
CollectionType.PLAYLISTS -> listOf(BaseItemKind.PLAYLIST)
|
||||
|
||||
else -> listOf()
|
||||
}
|
||||
val request =
|
||||
filter.applyTo(
|
||||
GetItemsRequest(
|
||||
parentId = item?.id,
|
||||
enableImageTypes = listOf(ImageType.PRIMARY, ImageType.THUMB),
|
||||
includeItemTypes = includeItemTypes,
|
||||
recursive = recursive,
|
||||
excludeItemIds = item?.let { listOf(item.id) },
|
||||
sortBy =
|
||||
buildList {
|
||||
add(sortAndDirection.sort)
|
||||
if (sortAndDirection.sort != ItemSortBy.SORT_NAME) {
|
||||
add(ItemSortBy.SORT_NAME)
|
||||
}
|
||||
if (item?.data?.collectionType == CollectionType.MOVIES) {
|
||||
add(ItemSortBy.PRODUCTION_YEAR)
|
||||
}
|
||||
},
|
||||
sortOrder =
|
||||
buildList {
|
||||
add(sortAndDirection.direction)
|
||||
if (sortAndDirection.sort != ItemSortBy.SORT_NAME) {
|
||||
add(SortOrder.ASCENDING)
|
||||
}
|
||||
if (item?.data?.collectionType == CollectionType.MOVIES) {
|
||||
add(SortOrder.ASCENDING)
|
||||
}
|
||||
},
|
||||
fields = SlimItemFields,
|
||||
),
|
||||
)
|
||||
val newPager =
|
||||
ApiRequestPager(
|
||||
api,
|
||||
request,
|
||||
GetItemsRequestHandler,
|
||||
viewModelScope,
|
||||
useSeriesForPrimary = true,
|
||||
)
|
||||
val newPager = createPager(sortAndDirection, recursive, filter)
|
||||
newPager.init()
|
||||
if (newPager.isNotEmpty()) newPager.getBlocking(0)
|
||||
withContext(Dispatchers.Main) {
|
||||
|
|
@ -216,6 +162,93 @@ class CollectionFolderViewModel
|
|||
}
|
||||
}
|
||||
|
||||
private fun createPager(
|
||||
sortAndDirection: SortAndDirection,
|
||||
recursive: Boolean,
|
||||
filter: GetItemsFilter,
|
||||
): ApiRequestPager<out Any> {
|
||||
val item = item.value
|
||||
return when (filter.override) {
|
||||
GetItemsFilterOverride.NONE -> {
|
||||
val includeItemTypes =
|
||||
when (item?.data?.collectionType) {
|
||||
CollectionType.MOVIES -> listOf(BaseItemKind.MOVIE)
|
||||
CollectionType.TVSHOWS -> listOf(BaseItemKind.SERIES)
|
||||
CollectionType.HOMEVIDEOS -> listOf(BaseItemKind.VIDEO)
|
||||
CollectionType.MUSIC ->
|
||||
listOf(
|
||||
BaseItemKind.AUDIO,
|
||||
BaseItemKind.MUSIC_ARTIST,
|
||||
BaseItemKind.MUSIC_ALBUM,
|
||||
)
|
||||
|
||||
CollectionType.BOXSETS -> listOf(BaseItemKind.BOX_SET)
|
||||
CollectionType.PLAYLISTS -> listOf(BaseItemKind.PLAYLIST)
|
||||
|
||||
else -> listOf()
|
||||
}
|
||||
val request =
|
||||
filter.applyTo(
|
||||
GetItemsRequest(
|
||||
parentId = item?.id,
|
||||
enableImageTypes = listOf(ImageType.PRIMARY, ImageType.THUMB),
|
||||
includeItemTypes = includeItemTypes,
|
||||
recursive = recursive,
|
||||
excludeItemIds = item?.let { listOf(item.id) },
|
||||
sortBy =
|
||||
buildList {
|
||||
add(sortAndDirection.sort)
|
||||
if (sortAndDirection.sort != ItemSortBy.SORT_NAME) {
|
||||
add(ItemSortBy.SORT_NAME)
|
||||
}
|
||||
if (item?.data?.collectionType == CollectionType.MOVIES) {
|
||||
add(ItemSortBy.PRODUCTION_YEAR)
|
||||
}
|
||||
},
|
||||
sortOrder =
|
||||
buildList {
|
||||
add(sortAndDirection.direction)
|
||||
if (sortAndDirection.sort != ItemSortBy.SORT_NAME) {
|
||||
add(SortOrder.ASCENDING)
|
||||
}
|
||||
if (item?.data?.collectionType == CollectionType.MOVIES) {
|
||||
add(SortOrder.ASCENDING)
|
||||
}
|
||||
},
|
||||
fields = SlimItemFields,
|
||||
),
|
||||
)
|
||||
val newPager =
|
||||
ApiRequestPager(
|
||||
api,
|
||||
request,
|
||||
GetItemsRequestHandler,
|
||||
viewModelScope,
|
||||
useSeriesForPrimary = true,
|
||||
)
|
||||
newPager
|
||||
}
|
||||
|
||||
GetItemsFilterOverride.PERSON -> {
|
||||
val request =
|
||||
filter.applyTo(
|
||||
GetPersonsRequest(
|
||||
enableImageTypes = listOf(ImageType.PRIMARY, ImageType.THUMB),
|
||||
),
|
||||
)
|
||||
val newPager =
|
||||
ApiRequestPager(
|
||||
api,
|
||||
request,
|
||||
GetPersonsHandler,
|
||||
viewModelScope,
|
||||
useSeriesForPrimary = true,
|
||||
)
|
||||
newPager
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun positionOfLetter(letter: Char): Int? =
|
||||
withContext(Dispatchers.IO) {
|
||||
item.value?.let { item ->
|
||||
|
|
@ -437,12 +470,14 @@ fun CollectionFolderGridContent(
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
SortByButton(
|
||||
sortOptions = sortOptions,
|
||||
current = sortAndDirection,
|
||||
onSortChange = onSortChange,
|
||||
modifier = Modifier,
|
||||
)
|
||||
if (sortOptions.isNotEmpty()) {
|
||||
SortByButton(
|
||||
sortOptions = sortOptions,
|
||||
current = sortAndDirection,
|
||||
onSortChange = onSortChange,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
CardGrid(
|
||||
pager = pager,
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.cards.GridCard
|
||||
import com.github.damontecres.wholphin.ui.detail.CardGrid
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.GetGenresRequestHandler
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import androidx.lifecycle.viewModelScope
|
|||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
|
||||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
|
|
@ -27,9 +29,7 @@ import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
|||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.main.HomePageContent
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.util.HomeRowLoadingState
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
|
@ -42,7 +42,7 @@ abstract class RecommendedViewModel(
|
|||
) : ViewModel() {
|
||||
abstract fun init()
|
||||
|
||||
abstract val rows: MutableStateFlow<MutableList<HomeRowLoadingState>>
|
||||
abstract val rows: MutableStateFlow<List<HomeRowLoadingState>>
|
||||
|
||||
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ import androidx.lifecycle.viewModelScope
|
|||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||
import com.github.damontecres.wholphin.util.GetResumeItemsRequestHandler
|
||||
import com.github.damontecres.wholphin.util.GetSuggestionsRequestHandler
|
||||
|
|
@ -57,13 +57,13 @@ class RecommendedMovieViewModel
|
|||
}
|
||||
|
||||
override val rows =
|
||||
MutableStateFlow<MutableList<HomeRowLoadingState>>(
|
||||
MutableStateFlow<List<HomeRowLoadingState>>(
|
||||
rowTitles
|
||||
.map {
|
||||
HomeRowLoadingState.Pending(
|
||||
context.getString(it),
|
||||
)
|
||||
}.toMutableList(),
|
||||
},
|
||||
)
|
||||
|
||||
override fun init() {
|
||||
|
|
@ -166,6 +166,9 @@ class RecommendedMovieViewModel
|
|||
val result =
|
||||
try {
|
||||
pager.init()
|
||||
if (pager.isNotEmpty()) {
|
||||
pager.getBlocking(0)
|
||||
}
|
||||
HomeRowLoadingState.Success(title, pager)
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Error fetching %s", title)
|
||||
|
|
@ -182,7 +185,7 @@ class RecommendedMovieViewModel
|
|||
row: HomeRowLoadingState,
|
||||
) {
|
||||
rows.update { current ->
|
||||
current.apply { set(position, row) }
|
||||
current.toMutableList().apply { set(position, row) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ import androidx.lifecycle.viewModelScope
|
|||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||
import com.github.damontecres.wholphin.util.GetNextUpRequestHandler
|
||||
import com.github.damontecres.wholphin.util.GetResumeItemsRequestHandler
|
||||
|
|
@ -59,13 +59,13 @@ class RecommendedTvShowViewModel
|
|||
}
|
||||
|
||||
override val rows =
|
||||
MutableStateFlow<MutableList<HomeRowLoadingState>>(
|
||||
MutableStateFlow<List<HomeRowLoadingState>>(
|
||||
rowTitles
|
||||
.map {
|
||||
HomeRowLoadingState.Pending(
|
||||
context.getString(it),
|
||||
)
|
||||
}.toMutableList(),
|
||||
},
|
||||
)
|
||||
|
||||
override fun init() {
|
||||
|
|
@ -210,7 +210,7 @@ class RecommendedTvShowViewModel
|
|||
row: HomeRowLoadingState,
|
||||
) {
|
||||
rows.update { current ->
|
||||
current.apply { set(position, row) }
|
||||
current.toMutableList().apply { set(position, row) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import android.widget.Toast
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.data.model.PlaylistCreator
|
||||
import com.github.damontecres.wholphin.services.PlaylistCreator
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
|
|
|
|||
|
|
@ -14,11 +14,11 @@ import androidx.tv.material3.MaterialTheme
|
|||
import androidx.tv.material3.ProvideTextStyle
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.ui.byteRateSuffixes
|
||||
import com.github.damontecres.wholphin.ui.components.ScrollableDialog
|
||||
import com.github.damontecres.wholphin.ui.formatBytes
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
import com.github.damontecres.wholphin.util.byteRateSuffixes
|
||||
import com.github.damontecres.wholphin.util.formatBytes
|
||||
import com.github.damontecres.wholphin.util.languageName
|
||||
import org.jellyfin.sdk.model.api.AudioSpatialFormat
|
||||
import org.jellyfin.sdk.model.api.MediaSourceInfo
|
||||
|
|
|
|||
|
|
@ -79,5 +79,6 @@ fun getStringRes(sort: ItemSortBy): Int =
|
|||
ItemSortBy.CRITIC_RATING -> R.string.critic_rating
|
||||
ItemSortBy.PLAY_COUNT -> R.string.play_count
|
||||
ItemSortBy.AIRED_EPISODE_ORDER -> R.string.aired_episode_order
|
||||
ItemSortBy.DEFAULT -> R.string.default_track
|
||||
else -> throw IllegalArgumentException("Unsupported sort option: $sort")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import com.github.damontecres.wholphin.data.ServerRepository
|
|||
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.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.TabRow
|
||||
|
|
@ -40,7 +41,6 @@ import com.github.damontecres.wholphin.ui.detail.livetv.TvGuideGrid
|
|||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.logTab
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.RememberTabManager
|
||||
|
|
|
|||
|
|
@ -288,7 +288,6 @@ fun buildMoreDialogItemsForHome(
|
|||
fun buildMoreDialogItemsForPerson(
|
||||
context: Context,
|
||||
person: Person,
|
||||
// favorite: Boolean,
|
||||
actions: MoreDialogActions,
|
||||
): List<DialogItem> =
|
||||
buildList {
|
||||
|
|
@ -301,14 +300,13 @@ fun buildMoreDialogItemsForPerson(
|
|||
actions.navigateTo(Destination.MediaItem(itemId, BaseItemKind.PERSON))
|
||||
},
|
||||
)
|
||||
// TODO need way to get person's favorite status
|
||||
// add(
|
||||
// DialogItem(
|
||||
// text = if (favorite) R.string.remove_favorite else R.string.add_favorite,
|
||||
// iconStringRes = R.string.fa_heart,
|
||||
// iconColor = if (favorite) Color.Red else Color.Unspecified,
|
||||
// ) {
|
||||
// actions.onClickFavorite.invoke(itemId, !favorite)
|
||||
// },
|
||||
// )
|
||||
add(
|
||||
DialogItem(
|
||||
text = if (person.favorite) R.string.remove_favorite else R.string.add_favorite,
|
||||
iconStringRes = R.string.fa_heart,
|
||||
iconColor = if (person.favorite) Color.Red else Color.Unspecified,
|
||||
) {
|
||||
actions.onClickFavorite.invoke(itemId, !person.favorite)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
|||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||
import com.github.damontecres.wholphin.data.model.GetItemsFilterOverride
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
|
|
@ -32,11 +33,15 @@ import com.github.damontecres.wholphin.ui.components.TabRow
|
|||
import com.github.damontecres.wholphin.ui.data.EpisodeSortOptions
|
||||
import com.github.damontecres.wholphin.ui.data.MovieSortOptions
|
||||
import com.github.damontecres.wholphin.ui.data.SeriesSortOptions
|
||||
import com.github.damontecres.wholphin.ui.data.SortAndDirection
|
||||
import com.github.damontecres.wholphin.ui.data.VideoSortOptions
|
||||
import com.github.damontecres.wholphin.ui.logTab
|
||||
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
|
||||
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||
import org.jellyfin.sdk.model.api.SortOrder
|
||||
|
||||
@Composable
|
||||
fun FavoritesPage(
|
||||
|
|
@ -59,6 +64,9 @@ fun FavoritesPage(
|
|||
stringResource(R.string.movies),
|
||||
stringResource(R.string.tv_shows),
|
||||
stringResource(R.string.episodes),
|
||||
stringResource(R.string.videos),
|
||||
stringResource(R.string.playlists),
|
||||
stringResource(R.string.people),
|
||||
)
|
||||
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
|
@ -165,6 +173,81 @@ fun FavoritesPage(
|
|||
},
|
||||
)
|
||||
}
|
||||
3 -> {
|
||||
CollectionFolderGrid(
|
||||
preferences = preferences,
|
||||
onClickItem = { _, item -> onClickItem.invoke(item) },
|
||||
itemId = "${NavDrawerItem.Favorites.id}_videos",
|
||||
initialFilter =
|
||||
GetItemsFilter(
|
||||
favorite = true,
|
||||
includeItemTypes = listOf(BaseItemKind.VIDEO),
|
||||
),
|
||||
showTitle = false,
|
||||
recursive = true,
|
||||
sortOptions = VideoSortOptions,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(start = 16.dp)
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
)
|
||||
}
|
||||
4 -> {
|
||||
CollectionFolderGrid(
|
||||
preferences = preferences,
|
||||
onClickItem = { _, item -> onClickItem.invoke(item) },
|
||||
itemId = "${NavDrawerItem.Favorites.id}_playlists",
|
||||
initialFilter =
|
||||
GetItemsFilter(
|
||||
favorite = true,
|
||||
includeItemTypes = listOf(BaseItemKind.PLAYLIST),
|
||||
),
|
||||
showTitle = false,
|
||||
recursive = true,
|
||||
sortOptions = VideoSortOptions,
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(start = 16.dp)
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
5 -> {
|
||||
CollectionFolderGrid(
|
||||
preferences = preferences,
|
||||
onClickItem = { _, item -> onClickItem.invoke(item) },
|
||||
itemId = "${NavDrawerItem.Favorites.id}_people",
|
||||
initialFilter =
|
||||
GetItemsFilter(
|
||||
favorite = true,
|
||||
override = GetItemsFilterOverride.PERSON,
|
||||
),
|
||||
initialSortAndDirection =
|
||||
SortAndDirection(
|
||||
ItemSortBy.DEFAULT,
|
||||
SortOrder.ASCENDING,
|
||||
),
|
||||
showTitle = false,
|
||||
recursive = true,
|
||||
sortOptions = listOf(),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(start = 16.dp)
|
||||
.fillMaxSize()
|
||||
.focusRequester(focusRequester),
|
||||
positionCallback = { columns, position ->
|
||||
showHeader = position < columns
|
||||
},
|
||||
)
|
||||
}
|
||||
else -> ErrorMessage("Invalid tab index $selectedTabIndex", null)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.focus.onFocusChanged
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -39,18 +41,22 @@ import coil3.compose.AsyncImage
|
|||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.ExpandableFaButton
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingRow
|
||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
||||
import com.github.damontecres.wholphin.ui.formatDate
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.rememberPosition
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||
|
|
@ -60,7 +66,6 @@ import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
|||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.RowLoadingState
|
||||
import com.github.damontecres.wholphin.util.formatDate
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
|
|
@ -78,6 +83,7 @@ class PersonViewModel
|
|||
constructor(
|
||||
api: ApiClient,
|
||||
val navigationManager: NavigationManager,
|
||||
private val favoriteWatchManager: FavoriteWatchManager,
|
||||
) : LoadingItemViewModel(api) {
|
||||
val movies = MutableLiveData<RowLoadingState>(RowLoadingState.Pending)
|
||||
val series = MutableLiveData<RowLoadingState>(RowLoadingState.Pending)
|
||||
|
|
@ -144,6 +150,13 @@ class PersonViewModel
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setFavorite(favorite: Boolean) {
|
||||
viewModelScope.launchIO {
|
||||
favoriteWatchManager.setFavorite(itemUuid, favorite)
|
||||
fetchAndSetItem(itemUuid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -180,6 +193,7 @@ fun PersonPage(
|
|||
birthdate = person.data.premiereDate?.toLocalDate(),
|
||||
deathdate = person.data.endDate?.toLocalDate(),
|
||||
birthPlace = person.data.productionLocations?.firstOrNull(),
|
||||
favorite = person.favorite,
|
||||
movies = movies,
|
||||
series = series,
|
||||
episodes = episodes,
|
||||
|
|
@ -187,6 +201,9 @@ fun PersonPage(
|
|||
viewModel.navigationManager.navigateTo(item.destination())
|
||||
},
|
||||
overviewOnClick = { showOverviewDialog = true },
|
||||
favoriteOnClick = {
|
||||
viewModel.setFavorite(!person.favorite)
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
AnimatedVisibility(showOverviewDialog) {
|
||||
|
|
@ -220,11 +237,13 @@ fun PersonPageContent(
|
|||
birthdate: LocalDate?,
|
||||
deathdate: LocalDate?,
|
||||
birthPlace: String?,
|
||||
favorite: Boolean,
|
||||
movies: RowLoadingState,
|
||||
series: RowLoadingState,
|
||||
episodes: RowLoadingState,
|
||||
onClickItem: (Int, BaseItem) -> Unit,
|
||||
overviewOnClick: () -> Unit,
|
||||
favoriteOnClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
|
@ -237,8 +256,10 @@ fun PersonPageContent(
|
|||
headerFocusRequester.tryRequestFocus()
|
||||
}
|
||||
}
|
||||
var focusedOnHeader by remember { mutableStateOf(true) }
|
||||
LazyColumn(
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
userScrollEnabled = !focusedOnHeader,
|
||||
modifier =
|
||||
modifier
|
||||
.padding(start = 16.dp),
|
||||
|
|
@ -251,13 +272,18 @@ fun PersonPageContent(
|
|||
birthdate = birthdate,
|
||||
birthPlace = birthPlace,
|
||||
deathdate = deathdate,
|
||||
favorite = favorite,
|
||||
overviewOnClick = overviewOnClick,
|
||||
favoriteOnClick = favoriteOnClick,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(max = 480.dp)
|
||||
.padding(top = 16.dp, bottom = 40.dp)
|
||||
.focusRequester(headerFocusRequester),
|
||||
.focusRequester(headerFocusRequester)
|
||||
.onFocusChanged {
|
||||
focusedOnHeader = it.hasFocus
|
||||
},
|
||||
)
|
||||
}
|
||||
item {
|
||||
|
|
@ -321,7 +347,9 @@ fun PersonHeader(
|
|||
birthdate: LocalDate?,
|
||||
deathdate: LocalDate?,
|
||||
birthPlace: String?,
|
||||
favorite: Boolean,
|
||||
overviewOnClick: () -> Unit,
|
||||
favoriteOnClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||
|
|
@ -380,9 +408,9 @@ fun PersonHeader(
|
|||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
birthPlace?.let {
|
||||
if (birthPlace.isNotNullOrBlank()) {
|
||||
Text(
|
||||
text = stringResource(R.string.birthplace) + ": $it",
|
||||
text = stringResource(R.string.birthplace) + ": $birthPlace",
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
maxLines = 1,
|
||||
|
|
@ -411,9 +439,12 @@ fun PersonHeader(
|
|||
)
|
||||
}
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
if (focused) {
|
||||
LaunchedEffect(Unit) {
|
||||
val buttonInteractionSource = remember { MutableInteractionSource() }
|
||||
val focused =
|
||||
interactionSource.collectIsFocusedAsState().value ||
|
||||
buttonInteractionSource.collectIsFocusedAsState().value
|
||||
LaunchedEffect(focused) {
|
||||
if (focused) {
|
||||
bringIntoViewRequester.bringIntoView()
|
||||
}
|
||||
}
|
||||
|
|
@ -424,6 +455,14 @@ fun PersonHeader(
|
|||
interactionSource = interactionSource,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
)
|
||||
ExpandableFaButton(
|
||||
title = if (favorite) R.string.remove_favorite else R.string.add_favorite,
|
||||
iconStringRes = R.string.fa_heart,
|
||||
onClick = favoriteOnClick,
|
||||
iconColor = if (favorite) Color.Red else Color.Unspecified,
|
||||
interactionSource = buttonInteractionSource,
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -440,6 +479,8 @@ private fun PersonPreview() {
|
|||
birthPlace = "Phoenix, Arizona, USA",
|
||||
deathdate = LocalDate.of(2025, 2, 1),
|
||||
overviewOnClick = {},
|
||||
favorite = true,
|
||||
favoriteOnClick = {},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ import androidx.tv.material3.surfaceColorAtElevation
|
|||
import coil3.compose.AsyncImage
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.DefaultItemFields
|
||||
import com.github.damontecres.wholphin.ui.cards.ItemCardImage
|
||||
import com.github.damontecres.wholphin.ui.components.DialogItem
|
||||
|
|
@ -70,7 +71,6 @@ import com.github.damontecres.wholphin.ui.enableMarquee
|
|||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.roundMinutes
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
|
|
|
|||
|
|
@ -34,17 +34,17 @@ import androidx.tv.material3.Text
|
|||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.seasonEpisode
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.seasonEpisode
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ import androidx.lifecycle.viewModelScope
|
|||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.WholphinApplication
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.AppColors
|
||||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisode
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.ui.toServerString
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
|
|
|
|||
|
|
@ -36,9 +36,9 @@ import com.github.damontecres.wholphin.data.model.BaseItem
|
|||
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.seasonEpisode
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.seasonEpisode
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.github.damontecres.wholphin.ui.detail.movie
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -54,6 +53,7 @@ import com.github.damontecres.wholphin.data.model.RemoteTrailer
|
|||
import com.github.damontecres.wholphin.data.model.Trailer
|
||||
import com.github.damontecres.wholphin.data.model.chooseSource
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.TrailerService
|
||||
import com.github.damontecres.wholphin.ui.Cards
|
||||
import com.github.damontecres.wholphin.ui.cards.ChapterRow
|
||||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||
|
|
@ -277,22 +277,8 @@ fun MovieDetails(
|
|||
items = items,
|
||||
)
|
||||
},
|
||||
trailerOnClick = { trailer ->
|
||||
when (trailer) {
|
||||
is LocalTrailer ->
|
||||
viewModel.navigateTo(
|
||||
Destination.Playback(
|
||||
itemId = trailer.baseItem.id,
|
||||
item = trailer.baseItem,
|
||||
positionMs = 0L,
|
||||
),
|
||||
)
|
||||
|
||||
is RemoteTrailer -> {
|
||||
val intent = Intent(Intent.ACTION_VIEW, trailer.url.toUri())
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
trailerOnClick = {
|
||||
TrailerService.onClick(context, it, viewModel::navigateTo)
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
|
|
@ -567,7 +553,7 @@ fun TrailerRow(
|
|||
LazyRow(
|
||||
state = state,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
|
||||
contentPadding = PaddingValues(horizontal = 24.dp, vertical = 8.dp),
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
|
|||
|
|
@ -32,11 +32,11 @@ import com.github.damontecres.wholphin.ui.components.DotSeparatedRow
|
|||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||
import com.github.damontecres.wholphin.ui.components.SimpleStarRating
|
||||
import com.github.damontecres.wholphin.ui.components.TitleValueText
|
||||
import com.github.damontecres.wholphin.ui.getAudioDisplay
|
||||
import com.github.damontecres.wholphin.ui.getSubtitleDisplay
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.roundMinutes
|
||||
import com.github.damontecres.wholphin.ui.timeRemaining
|
||||
import com.github.damontecres.wholphin.util.getAudioDisplay
|
||||
import com.github.damontecres.wholphin.util.getSubtitleDisplay
|
||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
import org.jellyfin.sdk.model.api.PersonKind
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
|
|
|
|||
|
|
@ -4,29 +4,27 @@ import android.content.Context
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ChosenStreams
|
||||
import com.github.damontecres.wholphin.data.ItemPlaybackRepository
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.Chapter
|
||||
import com.github.damontecres.wholphin.data.model.ItemPlayback
|
||||
import com.github.damontecres.wholphin.data.model.LocalTrailer
|
||||
import com.github.damontecres.wholphin.data.model.Person
|
||||
import com.github.damontecres.wholphin.data.model.RemoteTrailer
|
||||
import com.github.damontecres.wholphin.data.model.Trailer
|
||||
import com.github.damontecres.wholphin.preferences.ThemeSongVolume
|
||||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.PeopleFavorites
|
||||
import com.github.damontecres.wholphin.services.ThemeSongPlayer
|
||||
import com.github.damontecres.wholphin.services.TrailerService
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.ThemeSongPlayer
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -56,6 +54,8 @@ class MovieViewModel
|
|||
val itemPlaybackRepository: ItemPlaybackRepository,
|
||||
private val themeSongPlayer: ThemeSongPlayer,
|
||||
private val favoriteWatchManager: FavoriteWatchManager,
|
||||
private val peopleFavorites: PeopleFavorites,
|
||||
private val trailerService: TrailerService,
|
||||
@Assisted val itemId: UUID,
|
||||
) : ViewModel() {
|
||||
@AssistedFactory
|
||||
|
|
@ -107,39 +107,16 @@ class MovieViewModel
|
|||
loading.value = LoadingState.Success
|
||||
}
|
||||
viewModelScope.launchIO {
|
||||
val remoteTrailers =
|
||||
item.data.remoteTrailers
|
||||
?.mapNotNull { t ->
|
||||
t.url?.let { url ->
|
||||
val name =
|
||||
t.name
|
||||
// TODO would be nice to clean up the trailer name
|
||||
// ?.replace(item.name ?: "", "")
|
||||
// ?.removePrefix(" - ")
|
||||
?: context.getString(R.string.trailer)
|
||||
RemoteTrailer(name, url)
|
||||
}
|
||||
}.orEmpty()
|
||||
.sortedBy { it.name }
|
||||
val localTrailerCount = item.data.localTrailerCount ?: 0
|
||||
val localTrailers =
|
||||
if (localTrailerCount > 0) {
|
||||
api.userLibraryApi.getLocalTrailers(itemId).content.map {
|
||||
LocalTrailer(BaseItem.Companion.from(it, api))
|
||||
}
|
||||
} else {
|
||||
listOf()
|
||||
}
|
||||
val trailers = trailerService.getTrailers(item)
|
||||
withContext(Dispatchers.Main) {
|
||||
this@MovieViewModel.trailers.value = localTrailers + remoteTrailers
|
||||
this@MovieViewModel.trailers.value = trailers
|
||||
}
|
||||
}
|
||||
viewModelScope.launchIO {
|
||||
val people = peopleFavorites.getPeopleFor(item)
|
||||
this@MovieViewModel.people.setValueOnMain(people)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
people.value =
|
||||
item.data.people
|
||||
?.letNotEmpty { people ->
|
||||
people.map { Person.fromDto(it, api) }
|
||||
}.orEmpty()
|
||||
chapters.value = Chapter.fromDto(item.data, api)
|
||||
}
|
||||
if (!similar.isInitialized) {
|
||||
|
|
@ -171,7 +148,14 @@ class MovieViewModel
|
|||
favorite: Boolean,
|
||||
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||
favoriteWatchManager.setFavorite(itemId, favorite)
|
||||
val item = item.value
|
||||
fetchAndSetItem()
|
||||
if (item != null && itemId != item.id) {
|
||||
viewModelScope.launchIO {
|
||||
val people = peopleFavorites.getPeopleFor(item)
|
||||
this@MovieViewModel.people.setValueOnMain(people)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun savePlayVersion(
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ import com.github.damontecres.wholphin.data.model.chooseStream
|
|||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButtons
|
||||
import com.github.damontecres.wholphin.ui.components.TitleValueText
|
||||
import com.github.damontecres.wholphin.ui.getAudioDisplay
|
||||
import com.github.damontecres.wholphin.ui.getSubtitleDisplay
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.util.getAudioDisplay
|
||||
import com.github.damontecres.wholphin.util.getSubtitleDisplay
|
||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import kotlin.time.Duration
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ import com.github.damontecres.wholphin.data.model.BaseItem
|
|||
import com.github.damontecres.wholphin.ui.components.DotSeparatedRow
|
||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||
import com.github.damontecres.wholphin.ui.components.SimpleStarRating
|
||||
import com.github.damontecres.wholphin.ui.formatDateTime
|
||||
import com.github.damontecres.wholphin.ui.roundMinutes
|
||||
import com.github.damontecres.wholphin.ui.seasonEpisode
|
||||
import com.github.damontecres.wholphin.ui.timeRemaining
|
||||
import com.github.damontecres.wholphin.util.formatDateTime
|
||||
import com.github.damontecres.wholphin.util.seasonEpisode
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -46,7 +46,9 @@ import coil3.compose.AsyncImage
|
|||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.Person
|
||||
import com.github.damontecres.wholphin.data.model.Trailer
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.TrailerService
|
||||
import com.github.damontecres.wholphin.ui.Cards
|
||||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||
import com.github.damontecres.wholphin.ui.cards.PersonRow
|
||||
|
|
@ -71,6 +73,7 @@ import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
|||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForPerson
|
||||
import com.github.damontecres.wholphin.ui.detail.movie.TrailerRow
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
|
|
@ -102,6 +105,7 @@ fun SeriesDetails(
|
|||
|
||||
val item by viewModel.item.observeAsState()
|
||||
val seasons by viewModel.seasons.observeAsState(listOf())
|
||||
val trailers by viewModel.trailers.observeAsState(listOf())
|
||||
val people by viewModel.people.observeAsState(listOf())
|
||||
val similar by viewModel.similar.observeAsState(listOf())
|
||||
|
||||
|
|
@ -133,6 +137,7 @@ fun SeriesDetails(
|
|||
preferences = preferences,
|
||||
series = item,
|
||||
seasons = seasons,
|
||||
trailers = trailers,
|
||||
people = people,
|
||||
similar = similar,
|
||||
played = played,
|
||||
|
|
@ -174,6 +179,9 @@ fun SeriesDetails(
|
|||
val favorite = item.data.userData?.isFavorite ?: false
|
||||
viewModel.setFavorite(item.id, !favorite, null)
|
||||
},
|
||||
trailerOnClick = {
|
||||
TrailerService.onClick(context, it, viewModel::navigateTo)
|
||||
},
|
||||
moreActions =
|
||||
MoreDialogActions(
|
||||
navigateTo = { viewModel.navigateTo(it) },
|
||||
|
|
@ -244,7 +252,8 @@ fun SeriesDetails(
|
|||
private const val HEADER_ROW = 0
|
||||
private const val SEASONS_ROW = HEADER_ROW + 1
|
||||
private const val PEOPLE_ROW = SEASONS_ROW + 1
|
||||
private const val SIMILAR_ROW = PEOPLE_ROW + 1
|
||||
private const val TRAILER_ROW = PEOPLE_ROW + 1
|
||||
private const val SIMILAR_ROW = TRAILER_ROW + 1
|
||||
|
||||
@Composable
|
||||
fun SeriesDetailsContent(
|
||||
|
|
@ -252,6 +261,7 @@ fun SeriesDetailsContent(
|
|||
series: BaseItem,
|
||||
seasons: List<BaseItem>,
|
||||
similar: List<BaseItem>,
|
||||
trailers: List<Trailer>,
|
||||
people: List<Person>,
|
||||
played: Boolean,
|
||||
favorite: Boolean,
|
||||
|
|
@ -262,6 +272,7 @@ fun SeriesDetailsContent(
|
|||
playOnClick: () -> Unit,
|
||||
watchOnClick: () -> Unit,
|
||||
favoriteOnClick: () -> Unit,
|
||||
trailerOnClick: (Trailer) -> Unit,
|
||||
moreActions: MoreDialogActions,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -452,6 +463,21 @@ fun SeriesDetailsContent(
|
|||
)
|
||||
}
|
||||
}
|
||||
if (trailers.isNotEmpty()) {
|
||||
item {
|
||||
TrailerRow(
|
||||
trailers = trailers,
|
||||
onClickTrailer = {
|
||||
position = TRAILER_ROW
|
||||
trailerOnClick.invoke(it)
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequesters[TRAILER_ROW]),
|
||||
)
|
||||
}
|
||||
}
|
||||
if (similar.isNotEmpty()) {
|
||||
item {
|
||||
ItemRow(
|
||||
|
|
|
|||
|
|
@ -39,9 +39,9 @@ import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItems
|
|||
import com.github.damontecres.wholphin.ui.equalsNotNull
|
||||
import com.github.damontecres.wholphin.ui.indexOfFirstOrNull
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.seasonEpisode
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.seasonEpisode
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.sdk.model.api.ImageType
|
||||
|
|
|
|||
|
|
@ -49,11 +49,11 @@ import com.github.damontecres.wholphin.ui.cards.BannerCard
|
|||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.TabRow
|
||||
import com.github.damontecres.wholphin.ui.formatDateTime
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.logTab
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.formatDateTime
|
||||
import kotlin.time.Duration
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -10,25 +10,27 @@ import com.github.damontecres.wholphin.data.ServerRepository
|
|||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.data.model.ItemPlayback
|
||||
import com.github.damontecres.wholphin.data.model.Person
|
||||
import com.github.damontecres.wholphin.data.model.Trailer
|
||||
import com.github.damontecres.wholphin.preferences.ThemeSongVolume
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.PeopleFavorites
|
||||
import com.github.damontecres.wholphin.services.ThemeSongPlayer
|
||||
import com.github.damontecres.wholphin.services.TrailerService
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.detail.ItemViewModel
|
||||
import com.github.damontecres.wholphin.ui.equalsNotNull
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.ui.showToast
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.util.GetEpisodesRequestHandler
|
||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.ThemeSongPlayer
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -61,12 +63,15 @@ class SeriesViewModel
|
|||
private val itemPlaybackRepository: ItemPlaybackRepository,
|
||||
private val themeSongPlayer: ThemeSongPlayer,
|
||||
private val favoriteWatchManager: FavoriteWatchManager,
|
||||
private val peopleFavorites: PeopleFavorites,
|
||||
private val trailerService: TrailerService,
|
||||
) : ItemViewModel(api) {
|
||||
private lateinit var seriesId: UUID
|
||||
private lateinit var prefs: UserPreferences
|
||||
val loading = MutableLiveData<LoadingState>(LoadingState.Loading)
|
||||
val seasons = MutableLiveData<List<BaseItem>>(listOf())
|
||||
val episodes = MutableLiveData<EpisodeList>(EpisodeList.Loading)
|
||||
val trailers = MutableLiveData<List<Trailer>>(listOf())
|
||||
val people = MutableLiveData<List<Person>>(listOf())
|
||||
val similar = MutableLiveData<List<BaseItem>>()
|
||||
|
||||
|
|
@ -109,11 +114,16 @@ class SeriesViewModel
|
|||
this@SeriesViewModel.seasons.value = seasons
|
||||
episodes.value = episodeInfo
|
||||
loading.value = LoadingState.Success
|
||||
people.value =
|
||||
item.data.people
|
||||
?.letNotEmpty { people ->
|
||||
people.map { Person.fromDto(it, api) }
|
||||
}.orEmpty()
|
||||
}
|
||||
viewModelScope.launchIO {
|
||||
val trailers = trailerService.getTrailers(item)
|
||||
withContext(Dispatchers.Main) {
|
||||
this@SeriesViewModel.trailers.value = trailers
|
||||
}
|
||||
}
|
||||
viewModelScope.launchIO {
|
||||
val people = peopleFavorites.getPeopleFor(item)
|
||||
this@SeriesViewModel.people.setValueOnMain(people)
|
||||
}
|
||||
if (!similar.isInitialized) {
|
||||
viewModelScope.launchIO {
|
||||
|
|
@ -255,7 +265,11 @@ class SeriesViewModel
|
|||
if (listIndex != null) {
|
||||
refreshEpisode(itemId, listIndex)
|
||||
} else {
|
||||
fetchItem(seriesId)
|
||||
val item = fetchItem(seriesId)
|
||||
viewModelScope.launchIO {
|
||||
val people = peopleFavorites.getPeopleFor(item)
|
||||
this@SeriesViewModel.people.setValueOnMain(people)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,15 +62,15 @@ import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
|
|||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.formatDateTime
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.roundMinutes
|
||||
import com.github.damontecres.wholphin.ui.seasonEpisode
|
||||
import com.github.damontecres.wholphin.ui.timeRemaining
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.HomeRowLoadingState
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.formatDateTime
|
||||
import com.github.damontecres.wholphin.util.seasonEpisode
|
||||
import kotlinx.coroutines.delay
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.MediaType
|
||||
|
|
@ -360,9 +360,15 @@ fun HomePageContent(
|
|||
),
|
||||
).onFocusChanged {
|
||||
if (it.isFocused) {
|
||||
val nonEmptyRowBefore =
|
||||
homeRows
|
||||
.subList(0, rowIndex)
|
||||
.count {
|
||||
it is HomeRowLoadingState.Success && it.items.isEmpty()
|
||||
}
|
||||
onFocusPosition?.invoke(
|
||||
RowColumn(
|
||||
rowIndex,
|
||||
rowIndex - nonEmptyRowBefore,
|
||||
index,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,12 +9,12 @@ import com.github.damontecres.wholphin.data.NavDrawerItemRepository
|
|||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.util.HomeRowLoadingState
|
||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import androidx.tv.material3.Text
|
|||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.Cards
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import com.github.damontecres.wholphin.ui.cards.EpisodeCard
|
||||
|
|
@ -42,7 +43,6 @@ import com.github.damontecres.wholphin.ui.components.SearchEditTextBox
|
|||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.rememberPosition
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import androidx.navigation3.ui.NavDisplay
|
|||
import com.github.damontecres.wholphin.data.model.JellyfinServer
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import org.jellyfin.sdk.model.api.DeviceProfile
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,9 @@ import com.github.damontecres.wholphin.data.NavDrawerItemRepository
|
|||
import com.github.damontecres.wholphin.data.model.JellyfinServer
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.FontAwesome
|
||||
import com.github.damontecres.wholphin.ui.TimeFormatter
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
|
||||
|
|
@ -76,7 +78,6 @@ import com.github.damontecres.wholphin.ui.setValueOnMain
|
|||
import com.github.damontecres.wholphin.ui.spacedByWithFooter
|
||||
import com.github.damontecres.wholphin.ui.toServerString
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.TimeFormatter
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
|
|||
|
|
@ -39,13 +39,13 @@ import androidx.tv.material3.Text
|
|||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.ui.AppColors
|
||||
import com.github.damontecres.wholphin.ui.abbreviateNumber
|
||||
import com.github.damontecres.wholphin.ui.components.DialogItem
|
||||
import com.github.damontecres.wholphin.ui.components.DialogItemDivider
|
||||
import com.github.damontecres.wholphin.ui.components.EditTextBox
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.abbreviateNumber
|
||||
import org.jellyfin.sdk.model.api.RemoteSubtitleInfo
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -65,9 +65,9 @@ import com.github.damontecres.wholphin.ui.AppColors
|
|||
import com.github.damontecres.wholphin.ui.indexOfFirstOrNull
|
||||
import com.github.damontecres.wholphin.ui.seekBack
|
||||
import com.github.damontecres.wholphin.ui.seekForward
|
||||
import com.github.damontecres.wholphin.ui.stringRes
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.stringRes
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
|
|||
|
|
@ -51,13 +51,13 @@ import com.github.damontecres.wholphin.data.model.Chapter
|
|||
import com.github.damontecres.wholphin.data.model.ItemPlayback
|
||||
import com.github.damontecres.wholphin.data.model.Playlist
|
||||
import com.github.damontecres.wholphin.ui.AppColors
|
||||
import com.github.damontecres.wholphin.ui.TimeFormatter
|
||||
import com.github.damontecres.wholphin.ui.cards.ChapterCard
|
||||
import com.github.damontecres.wholphin.ui.cards.SeasonCard
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.TimeFormatter
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import org.jellyfin.sdk.model.api.MediaSegmentDto
|
||||
|
|
|
|||
|
|
@ -75,11 +75,11 @@ import com.github.damontecres.wholphin.ui.components.LoadingPage
|
|||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings.applyToMpv
|
||||
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings.toSubtitleStyle
|
||||
import com.github.damontecres.wholphin.ui.seasonEpisode
|
||||
import com.github.damontecres.wholphin.ui.stringRes
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.seasonEpisode
|
||||
import com.github.damontecres.wholphin.util.stringRes
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import com.github.damontecres.wholphin.data.model.BaseItem
|
|||
import com.github.damontecres.wholphin.data.model.Chapter
|
||||
import com.github.damontecres.wholphin.data.model.ItemPlayback
|
||||
import com.github.damontecres.wholphin.data.model.Playlist
|
||||
import com.github.damontecres.wholphin.data.model.PlaylistCreator
|
||||
import com.github.damontecres.wholphin.data.model.TrackIndex
|
||||
import com.github.damontecres.wholphin.data.model.chooseSource
|
||||
import com.github.damontecres.wholphin.data.model.chooseStream
|
||||
|
|
@ -35,9 +34,13 @@ import com.github.damontecres.wholphin.preferences.PlayerBackend
|
|||
import com.github.damontecres.wholphin.preferences.ShowNextUpWhen
|
||||
import com.github.damontecres.wholphin.preferences.SkipSegmentBehavior
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.PlayerFactory
|
||||
import com.github.damontecres.wholphin.services.PlaylistCreator
|
||||
import com.github.damontecres.wholphin.ui.formatDateTime
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.seasonEpisodePadded
|
||||
import com.github.damontecres.wholphin.ui.seekBack
|
||||
import com.github.damontecres.wholphin.ui.seekForward
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
|
|
@ -47,13 +50,10 @@ import com.github.damontecres.wholphin.util.EqualityMutableLiveData
|
|||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.PlayerFactory
|
||||
import com.github.damontecres.wholphin.util.TrackActivityPlaybackListener
|
||||
import com.github.damontecres.wholphin.util.TrackSupport
|
||||
import com.github.damontecres.wholphin.util.checkForSupport
|
||||
import com.github.damontecres.wholphin.util.formatDateTime
|
||||
import com.github.damontecres.wholphin.util.mpv.mpvDeviceProfile
|
||||
import com.github.damontecres.wholphin.util.seasonEpisodePadded
|
||||
import com.github.damontecres.wholphin.util.subtitleMimeTypes
|
||||
import com.github.damontecres.wholphin.util.supportItemKinds
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@ import com.github.damontecres.wholphin.data.model.NavPinType
|
|||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||
import com.github.damontecres.wholphin.preferences.resetSubtitles
|
||||
import com.github.damontecres.wholphin.preferences.updateSubtitlePreferences
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.detail.DebugViewModel.Companion.sendAppLogs
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.RememberTabManager
|
||||
|
|
|
|||
|
|
@ -50,22 +50,22 @@ import androidx.tv.material3.Text
|
|||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.DownloadCallback
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.Release
|
||||
import com.github.damontecres.wholphin.services.UpdateChecker
|
||||
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
||||
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.formatBytes
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||
import com.github.damontecres.wholphin.util.DownloadCallback
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import com.github.damontecres.wholphin.util.Release
|
||||
import com.github.damontecres.wholphin.util.UpdateChecker
|
||||
import com.github.damontecres.wholphin.util.Version
|
||||
import com.github.damontecres.wholphin.util.formatBytes
|
||||
import com.mikepenz.markdown.m3.Markdown
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ import com.github.damontecres.wholphin.R
|
|||
import com.github.damontecres.wholphin.data.JellyfinServerDao
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinServer
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.showToast
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import com.github.damontecres.wholphin.data.JellyfinServerDao
|
|||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinServer
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import org.jellyfin.sdk.api.client.Response
|
|||
import org.jellyfin.sdk.api.client.extensions.genresApi
|
||||
import org.jellyfin.sdk.api.client.extensions.itemsApi
|
||||
import org.jellyfin.sdk.api.client.extensions.liveTvApi
|
||||
import org.jellyfin.sdk.api.client.extensions.personsApi
|
||||
import org.jellyfin.sdk.api.client.extensions.playlistsApi
|
||||
import org.jellyfin.sdk.api.client.extensions.suggestionsApi
|
||||
import org.jellyfin.sdk.api.client.extensions.tvShowsApi
|
||||
|
|
@ -28,6 +29,7 @@ import org.jellyfin.sdk.model.api.request.GetEpisodesRequest
|
|||
import org.jellyfin.sdk.model.api.request.GetGenresRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetNextUpRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetPersonsRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetPlaylistItemsRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetResumeItemsRequest
|
||||
import org.jellyfin.sdk.model.api.request.GetSuggestionsRequest
|
||||
|
|
@ -365,3 +367,22 @@ val GetProgramsDtoHandler =
|
|||
request: GetProgramsDto,
|
||||
): Response<BaseItemDtoQueryResult> = api.liveTvApi.getPrograms(request)
|
||||
}
|
||||
|
||||
val GetPersonsHandler =
|
||||
object : RequestHandler<GetPersonsRequest> {
|
||||
override fun prepare(
|
||||
request: GetPersonsRequest,
|
||||
startIndex: Int,
|
||||
limit: Int,
|
||||
enableTotalRecordCount: Boolean,
|
||||
): GetPersonsRequest =
|
||||
request.copy(
|
||||
// startIndex = startIndex,
|
||||
limit = limit,
|
||||
)
|
||||
|
||||
override suspend fun execute(
|
||||
api: ApiClient,
|
||||
request: GetPersonsRequest,
|
||||
): Response<BaseItemDtoQueryResult> = api.personsApi.getPersons((request))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package com.github.damontecres.wholphin.util
|
|||
|
||||
import android.content.Context
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.hilt.AppModule
|
||||
import com.github.damontecres.wholphin.services.hilt.AppModule
|
||||
import com.google.auto.service.AutoService
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.OkHttpClient
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@
|
|||
<string name="playback_speed">Playback Speed</string>
|
||||
<string name="playback">Playback</string>
|
||||
<string name="playlist">Playlist</string>
|
||||
<string name="playlists">Playlists</string>
|
||||
<string name="preview">Preview</string>
|
||||
<string name="profile_specific_settings">User Profile Settings</string>
|
||||
<string name="queue">Queue</string>
|
||||
|
|
@ -135,6 +136,7 @@
|
|||
<string name="version">Version</string>
|
||||
<string name="video_scale">Video Scale</string>
|
||||
<string name="video">Video</string>
|
||||
<string name="videos">Videos</string>
|
||||
<string name="watch_live">Watch live</string>
|
||||
<string name="years_old">%1$d years old</string>
|
||||
<string name="play_with_transcoding">Play with transcoding</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue