mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
More favorite tabs & add people to favorites (#210)
Adds Video, Playlists, & People tabs to the favorites page Also adds ability to add people to favorites via context menu or on their page Closes #199
This commit is contained in:
parent
655720fc05
commit
19b712d54b
13 changed files with 356 additions and 102 deletions
|
|
@ -6,6 +6,7 @@ import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.UseSerializers
|
import kotlinx.serialization.UseSerializers
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||||
|
import org.jellyfin.sdk.model.api.request.GetPersonsRequest
|
||||||
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
|
@ -19,6 +20,7 @@ data class GetItemsFilter(
|
||||||
val studios: List<UUID>? = null,
|
val studios: List<UUID>? = null,
|
||||||
val tags: List<String>? = null,
|
val tags: List<String>? = null,
|
||||||
val includeItemTypes: List<BaseItemKind>? = null,
|
val includeItemTypes: List<BaseItemKind>? = null,
|
||||||
|
val override: GetItemsFilterOverride = GetItemsFilterOverride.NONE,
|
||||||
) {
|
) {
|
||||||
fun applyTo(req: GetItemsRequest) =
|
fun applyTo(req: GetItemsRequest) =
|
||||||
req.copy(
|
req.copy(
|
||||||
|
|
@ -31,4 +33,14 @@ data class GetItemsFilter(
|
||||||
studioIds = studios,
|
studioIds = studios,
|
||||||
tags = tags,
|
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 role: String?,
|
||||||
val type: PersonKind,
|
val type: PersonKind,
|
||||||
val imageUrl: String?,
|
val imageUrl: String?,
|
||||||
|
val favorite: Boolean,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
fun fromDto(
|
fun fromDto(
|
||||||
|
|
@ -25,6 +26,21 @@ data class Person(
|
||||||
role = dto.role,
|
role = dto.role,
|
||||||
type = dto.type,
|
type = dto.type,
|
||||||
imageUrl = api.imageApi.getItemImageUrl(dto.id, ImageType.PRIMARY),
|
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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,8 +72,8 @@ fun PersonCard(
|
||||||
ItemCardImage(
|
ItemCardImage(
|
||||||
imageUrl = item.imageUrl,
|
imageUrl = item.imageUrl,
|
||||||
name = item.name,
|
name = item.name,
|
||||||
showOverlay = false,
|
showOverlay = true,
|
||||||
favorite = false,
|
favorite = item.favorite,
|
||||||
watched = false,
|
watched = false,
|
||||||
unwatchedCount = -1,
|
unwatchedCount = -1,
|
||||||
watchedPercent = null,
|
watchedPercent = null,
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import com.github.damontecres.wholphin.data.LibraryDisplayInfoDao
|
||||||
import com.github.damontecres.wholphin.data.ServerRepository
|
import com.github.damontecres.wholphin.data.ServerRepository
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||||
|
import com.github.damontecres.wholphin.data.model.GetItemsFilterOverride
|
||||||
import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
|
import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||||
|
|
@ -53,6 +54,7 @@ import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
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.LoadingExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingState
|
import com.github.damontecres.wholphin.util.LoadingState
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
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.MediaType
|
||||||
import org.jellyfin.sdk.model.api.SortOrder
|
import org.jellyfin.sdk.model.api.SortOrder
|
||||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||||
|
import org.jellyfin.sdk.model.api.request.GetPersonsRequest
|
||||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -107,13 +110,12 @@ class CollectionFolderViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
val sortAndDirection =
|
val sortAndDirection =
|
||||||
if (initialSortAndDirection == null) {
|
initialSortAndDirection
|
||||||
serverRepository.currentUser.value?.let { user ->
|
?: (
|
||||||
libraryDisplayInfoDao.getItem(user, itemId)?.sortAndDirection
|
serverRepository.currentUser.value?.let { user ->
|
||||||
} ?: SortAndDirection.DEFAULT
|
libraryDisplayInfoDao.getItem(user, itemId)?.sortAndDirection
|
||||||
} else {
|
} ?: SortAndDirection.DEFAULT
|
||||||
SortAndDirection.DEFAULT
|
)
|
||||||
}
|
|
||||||
|
|
||||||
loadResults(sortAndDirection, recursive, filter)
|
loadResults(sortAndDirection, recursive, filter)
|
||||||
}
|
}
|
||||||
|
|
@ -143,7 +145,6 @@ class CollectionFolderViewModel
|
||||||
recursive: Boolean,
|
recursive: Boolean,
|
||||||
filter: GetItemsFilter,
|
filter: GetItemsFilter,
|
||||||
) {
|
) {
|
||||||
val item = item.value
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
pager.value = listOf()
|
pager.value = listOf()
|
||||||
|
|
@ -151,62 +152,7 @@ class CollectionFolderViewModel
|
||||||
this@CollectionFolderViewModel.sortAndDirection.value = sortAndDirection
|
this@CollectionFolderViewModel.sortAndDirection.value = sortAndDirection
|
||||||
this@CollectionFolderViewModel.filter.value = filter
|
this@CollectionFolderViewModel.filter.value = filter
|
||||||
}
|
}
|
||||||
val includeItemTypes =
|
val newPager = createPager(sortAndDirection, recursive, filter)
|
||||||
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.init()
|
newPager.init()
|
||||||
if (newPager.isNotEmpty()) newPager.getBlocking(0)
|
if (newPager.isNotEmpty()) newPager.getBlocking(0)
|
||||||
withContext(Dispatchers.Main) {
|
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? =
|
suspend fun positionOfLetter(letter: Char): Int? =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
item.value?.let { item ->
|
item.value?.let { item ->
|
||||||
|
|
@ -437,12 +470,14 @@ fun CollectionFolderGridContent(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
SortByButton(
|
if (sortOptions.isNotEmpty()) {
|
||||||
sortOptions = sortOptions,
|
SortByButton(
|
||||||
current = sortAndDirection,
|
sortOptions = sortOptions,
|
||||||
onSortChange = onSortChange,
|
current = sortAndDirection,
|
||||||
modifier = Modifier,
|
onSortChange = onSortChange,
|
||||||
)
|
modifier = Modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CardGrid(
|
CardGrid(
|
||||||
pager = pager,
|
pager = pager,
|
||||||
|
|
|
||||||
|
|
@ -79,5 +79,6 @@ fun getStringRes(sort: ItemSortBy): Int =
|
||||||
ItemSortBy.CRITIC_RATING -> R.string.critic_rating
|
ItemSortBy.CRITIC_RATING -> R.string.critic_rating
|
||||||
ItemSortBy.PLAY_COUNT -> R.string.play_count
|
ItemSortBy.PLAY_COUNT -> R.string.play_count
|
||||||
ItemSortBy.AIRED_EPISODE_ORDER -> R.string.aired_episode_order
|
ItemSortBy.AIRED_EPISODE_ORDER -> R.string.aired_episode_order
|
||||||
|
ItemSortBy.DEFAULT -> R.string.default_track
|
||||||
else -> throw IllegalArgumentException("Unsupported sort option: $sort")
|
else -> throw IllegalArgumentException("Unsupported sort option: $sort")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -288,7 +288,6 @@ fun buildMoreDialogItemsForHome(
|
||||||
fun buildMoreDialogItemsForPerson(
|
fun buildMoreDialogItemsForPerson(
|
||||||
context: Context,
|
context: Context,
|
||||||
person: Person,
|
person: Person,
|
||||||
// favorite: Boolean,
|
|
||||||
actions: MoreDialogActions,
|
actions: MoreDialogActions,
|
||||||
): List<DialogItem> =
|
): List<DialogItem> =
|
||||||
buildList {
|
buildList {
|
||||||
|
|
@ -301,14 +300,13 @@ fun buildMoreDialogItemsForPerson(
|
||||||
actions.navigateTo(Destination.MediaItem(itemId, BaseItemKind.PERSON))
|
actions.navigateTo(Destination.MediaItem(itemId, BaseItemKind.PERSON))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
// TODO need way to get person's favorite status
|
add(
|
||||||
// add(
|
DialogItem(
|
||||||
// DialogItem(
|
text = if (person.favorite) R.string.remove_favorite else R.string.add_favorite,
|
||||||
// text = if (favorite) R.string.remove_favorite else R.string.add_favorite,
|
iconStringRes = R.string.fa_heart,
|
||||||
// iconStringRes = R.string.fa_heart,
|
iconColor = if (person.favorite) Color.Red else Color.Unspecified,
|
||||||
// iconColor = if (favorite) Color.Red else Color.Unspecified,
|
) {
|
||||||
// ) {
|
actions.onClickFavorite.invoke(itemId, !person.favorite)
|
||||||
// actions.onClickFavorite.invoke(itemId, !favorite)
|
},
|
||||||
// },
|
)
|
||||||
// )
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import com.github.damontecres.wholphin.R
|
import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||||
|
import com.github.damontecres.wholphin.data.model.GetItemsFilterOverride
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
|
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
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.EpisodeSortOptions
|
||||||
import com.github.damontecres.wholphin.ui.data.MovieSortOptions
|
import com.github.damontecres.wholphin.ui.data.MovieSortOptions
|
||||||
import com.github.damontecres.wholphin.ui.data.SeriesSortOptions
|
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.logTab
|
||||||
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
|
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
|
||||||
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
|
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
|
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||||
|
import org.jellyfin.sdk.model.api.SortOrder
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun FavoritesPage(
|
fun FavoritesPage(
|
||||||
|
|
@ -59,6 +64,9 @@ fun FavoritesPage(
|
||||||
stringResource(R.string.movies),
|
stringResource(R.string.movies),
|
||||||
stringResource(R.string.tv_shows),
|
stringResource(R.string.tv_shows),
|
||||||
stringResource(R.string.episodes),
|
stringResource(R.string.episodes),
|
||||||
|
stringResource(R.string.videos),
|
||||||
|
stringResource(R.string.playlists),
|
||||||
|
stringResource(R.string.people),
|
||||||
)
|
)
|
||||||
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
var selectedTabIndex by rememberSaveable { mutableIntStateOf(rememberedTabIndex) }
|
||||||
val focusRequester = remember { FocusRequester() }
|
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)
|
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.draw.clip
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
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.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
@ -43,11 +45,13 @@ import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||||
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
||||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
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.LoadingPage
|
||||||
import com.github.damontecres.wholphin.ui.components.LoadingRow
|
import com.github.damontecres.wholphin.ui.components.LoadingRow
|
||||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialog
|
||||||
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
import com.github.damontecres.wholphin.ui.data.ItemDetailsDialogInfo
|
||||||
|
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||||
import com.github.damontecres.wholphin.ui.launchIO
|
import com.github.damontecres.wholphin.ui.launchIO
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||||
|
|
@ -56,6 +60,7 @@ import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||||
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||||
|
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingState
|
import com.github.damontecres.wholphin.util.LoadingState
|
||||||
|
|
@ -78,6 +83,7 @@ class PersonViewModel
|
||||||
constructor(
|
constructor(
|
||||||
api: ApiClient,
|
api: ApiClient,
|
||||||
val navigationManager: NavigationManager,
|
val navigationManager: NavigationManager,
|
||||||
|
private val favoriteWatchManager: FavoriteWatchManager,
|
||||||
) : LoadingItemViewModel(api) {
|
) : LoadingItemViewModel(api) {
|
||||||
val movies = MutableLiveData<RowLoadingState>(RowLoadingState.Pending)
|
val movies = MutableLiveData<RowLoadingState>(RowLoadingState.Pending)
|
||||||
val series = 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
|
@Composable
|
||||||
|
|
@ -180,6 +193,7 @@ fun PersonPage(
|
||||||
birthdate = person.data.premiereDate?.toLocalDate(),
|
birthdate = person.data.premiereDate?.toLocalDate(),
|
||||||
deathdate = person.data.endDate?.toLocalDate(),
|
deathdate = person.data.endDate?.toLocalDate(),
|
||||||
birthPlace = person.data.productionLocations?.firstOrNull(),
|
birthPlace = person.data.productionLocations?.firstOrNull(),
|
||||||
|
favorite = person.favorite,
|
||||||
movies = movies,
|
movies = movies,
|
||||||
series = series,
|
series = series,
|
||||||
episodes = episodes,
|
episodes = episodes,
|
||||||
|
|
@ -187,6 +201,9 @@ fun PersonPage(
|
||||||
viewModel.navigationManager.navigateTo(item.destination())
|
viewModel.navigationManager.navigateTo(item.destination())
|
||||||
},
|
},
|
||||||
overviewOnClick = { showOverviewDialog = true },
|
overviewOnClick = { showOverviewDialog = true },
|
||||||
|
favoriteOnClick = {
|
||||||
|
viewModel.setFavorite(!person.favorite)
|
||||||
|
},
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
)
|
)
|
||||||
AnimatedVisibility(showOverviewDialog) {
|
AnimatedVisibility(showOverviewDialog) {
|
||||||
|
|
@ -220,11 +237,13 @@ fun PersonPageContent(
|
||||||
birthdate: LocalDate?,
|
birthdate: LocalDate?,
|
||||||
deathdate: LocalDate?,
|
deathdate: LocalDate?,
|
||||||
birthPlace: String?,
|
birthPlace: String?,
|
||||||
|
favorite: Boolean,
|
||||||
movies: RowLoadingState,
|
movies: RowLoadingState,
|
||||||
series: RowLoadingState,
|
series: RowLoadingState,
|
||||||
episodes: RowLoadingState,
|
episodes: RowLoadingState,
|
||||||
onClickItem: (Int, BaseItem) -> Unit,
|
onClickItem: (Int, BaseItem) -> Unit,
|
||||||
overviewOnClick: () -> Unit,
|
overviewOnClick: () -> Unit,
|
||||||
|
favoriteOnClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
@ -237,8 +256,10 @@ fun PersonPageContent(
|
||||||
headerFocusRequester.tryRequestFocus()
|
headerFocusRequester.tryRequestFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var focusedOnHeader by remember { mutableStateOf(true) }
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
userScrollEnabled = !focusedOnHeader,
|
||||||
modifier =
|
modifier =
|
||||||
modifier
|
modifier
|
||||||
.padding(start = 16.dp),
|
.padding(start = 16.dp),
|
||||||
|
|
@ -251,13 +272,18 @@ fun PersonPageContent(
|
||||||
birthdate = birthdate,
|
birthdate = birthdate,
|
||||||
birthPlace = birthPlace,
|
birthPlace = birthPlace,
|
||||||
deathdate = deathdate,
|
deathdate = deathdate,
|
||||||
|
favorite = favorite,
|
||||||
overviewOnClick = overviewOnClick,
|
overviewOnClick = overviewOnClick,
|
||||||
|
favoriteOnClick = favoriteOnClick,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.heightIn(max = 480.dp)
|
.heightIn(max = 480.dp)
|
||||||
.padding(top = 16.dp, bottom = 40.dp)
|
.padding(top = 16.dp, bottom = 40.dp)
|
||||||
.focusRequester(headerFocusRequester),
|
.focusRequester(headerFocusRequester)
|
||||||
|
.onFocusChanged {
|
||||||
|
focusedOnHeader = it.hasFocus
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
item {
|
item {
|
||||||
|
|
@ -321,7 +347,9 @@ fun PersonHeader(
|
||||||
birthdate: LocalDate?,
|
birthdate: LocalDate?,
|
||||||
deathdate: LocalDate?,
|
deathdate: LocalDate?,
|
||||||
birthPlace: String?,
|
birthPlace: String?,
|
||||||
|
favorite: Boolean,
|
||||||
overviewOnClick: () -> Unit,
|
overviewOnClick: () -> Unit,
|
||||||
|
favoriteOnClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||||
|
|
@ -380,9 +408,9 @@ fun PersonHeader(
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
birthPlace?.let {
|
if (birthPlace.isNotNullOrBlank()) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.birthplace) + ": $it",
|
text = stringResource(R.string.birthplace) + ": $birthPlace",
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
|
|
@ -411,9 +439,12 @@ fun PersonHeader(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
val focused by interactionSource.collectIsFocusedAsState()
|
val buttonInteractionSource = remember { MutableInteractionSource() }
|
||||||
if (focused) {
|
val focused =
|
||||||
LaunchedEffect(Unit) {
|
interactionSource.collectIsFocusedAsState().value ||
|
||||||
|
buttonInteractionSource.collectIsFocusedAsState().value
|
||||||
|
LaunchedEffect(focused) {
|
||||||
|
if (focused) {
|
||||||
bringIntoViewRequester.bringIntoView()
|
bringIntoViewRequester.bringIntoView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -424,6 +455,14 @@ fun PersonHeader(
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
modifier = Modifier.padding(top = 8.dp),
|
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",
|
birthPlace = "Phoenix, Arizona, USA",
|
||||||
deathdate = LocalDate.of(2025, 2, 1),
|
deathdate = LocalDate.of(2025, 2, 1),
|
||||||
overviewOnClick = {},
|
overviewOnClick = {},
|
||||||
|
favorite = true,
|
||||||
|
favoriteOnClick = {},
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import com.github.damontecres.wholphin.data.model.Trailer
|
||||||
import com.github.damontecres.wholphin.preferences.ThemeSongVolume
|
import com.github.damontecres.wholphin.preferences.ThemeSongVolume
|
||||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||||
import com.github.damontecres.wholphin.ui.launchIO
|
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.Destination
|
||||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||||
|
|
@ -26,6 +25,7 @@ import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
import com.github.damontecres.wholphin.util.FavoriteWatchManager
|
||||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingState
|
import com.github.damontecres.wholphin.util.LoadingState
|
||||||
|
import com.github.damontecres.wholphin.util.PeopleFavorites
|
||||||
import com.github.damontecres.wholphin.util.ThemeSongPlayer
|
import com.github.damontecres.wholphin.util.ThemeSongPlayer
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
|
|
@ -56,6 +56,7 @@ class MovieViewModel
|
||||||
val itemPlaybackRepository: ItemPlaybackRepository,
|
val itemPlaybackRepository: ItemPlaybackRepository,
|
||||||
private val themeSongPlayer: ThemeSongPlayer,
|
private val themeSongPlayer: ThemeSongPlayer,
|
||||||
private val favoriteWatchManager: FavoriteWatchManager,
|
private val favoriteWatchManager: FavoriteWatchManager,
|
||||||
|
private val peopleFavorites: PeopleFavorites,
|
||||||
@Assisted val itemId: UUID,
|
@Assisted val itemId: UUID,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
|
|
@ -134,12 +135,11 @@ class MovieViewModel
|
||||||
this@MovieViewModel.trailers.value = localTrailers + remoteTrailers
|
this@MovieViewModel.trailers.value = localTrailers + remoteTrailers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
viewModelScope.launchIO {
|
||||||
|
val people = peopleFavorites.getPeopleFor(item)
|
||||||
|
this@MovieViewModel.people.setValueOnMain(people)
|
||||||
|
}
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
people.value =
|
|
||||||
item.data.people
|
|
||||||
?.letNotEmpty { people ->
|
|
||||||
people.map { Person.fromDto(it, api) }
|
|
||||||
}.orEmpty()
|
|
||||||
chapters.value = Chapter.fromDto(item.data, api)
|
chapters.value = Chapter.fromDto(item.data, api)
|
||||||
}
|
}
|
||||||
if (!similar.isInitialized) {
|
if (!similar.isInitialized) {
|
||||||
|
|
@ -171,7 +171,14 @@ class MovieViewModel
|
||||||
favorite: Boolean,
|
favorite: Boolean,
|
||||||
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||||
favoriteWatchManager.setFavorite(itemId, favorite)
|
favoriteWatchManager.setFavorite(itemId, favorite)
|
||||||
|
val item = item.value
|
||||||
fetchAndSetItem()
|
fetchAndSetItem()
|
||||||
|
if (item != null && itemId != item.id) {
|
||||||
|
viewModelScope.launchIO {
|
||||||
|
val people = peopleFavorites.getPeopleFor(item)
|
||||||
|
this@MovieViewModel.people.setValueOnMain(people)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun savePlayVersion(
|
fun savePlayVersion(
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||||
import com.github.damontecres.wholphin.ui.detail.ItemViewModel
|
import com.github.damontecres.wholphin.ui.detail.ItemViewModel
|
||||||
import com.github.damontecres.wholphin.ui.equalsNotNull
|
import com.github.damontecres.wholphin.ui.equalsNotNull
|
||||||
import com.github.damontecres.wholphin.ui.launchIO
|
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.Destination
|
||||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||||
|
|
@ -28,6 +27,7 @@ import com.github.damontecres.wholphin.util.GetEpisodesRequestHandler
|
||||||
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingState
|
import com.github.damontecres.wholphin.util.LoadingState
|
||||||
|
import com.github.damontecres.wholphin.util.PeopleFavorites
|
||||||
import com.github.damontecres.wholphin.util.ThemeSongPlayer
|
import com.github.damontecres.wholphin.util.ThemeSongPlayer
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
|
@ -61,6 +61,7 @@ class SeriesViewModel
|
||||||
private val itemPlaybackRepository: ItemPlaybackRepository,
|
private val itemPlaybackRepository: ItemPlaybackRepository,
|
||||||
private val themeSongPlayer: ThemeSongPlayer,
|
private val themeSongPlayer: ThemeSongPlayer,
|
||||||
private val favoriteWatchManager: FavoriteWatchManager,
|
private val favoriteWatchManager: FavoriteWatchManager,
|
||||||
|
private val peopleFavorites: PeopleFavorites,
|
||||||
) : ItemViewModel(api) {
|
) : ItemViewModel(api) {
|
||||||
private lateinit var seriesId: UUID
|
private lateinit var seriesId: UUID
|
||||||
private lateinit var prefs: UserPreferences
|
private lateinit var prefs: UserPreferences
|
||||||
|
|
@ -109,11 +110,10 @@ class SeriesViewModel
|
||||||
this@SeriesViewModel.seasons.value = seasons
|
this@SeriesViewModel.seasons.value = seasons
|
||||||
episodes.value = episodeInfo
|
episodes.value = episodeInfo
|
||||||
loading.value = LoadingState.Success
|
loading.value = LoadingState.Success
|
||||||
people.value =
|
}
|
||||||
item.data.people
|
viewModelScope.launchIO {
|
||||||
?.letNotEmpty { people ->
|
val people = peopleFavorites.getPeopleFor(item)
|
||||||
people.map { Person.fromDto(it, api) }
|
this@SeriesViewModel.people.setValueOnMain(people)
|
||||||
}.orEmpty()
|
|
||||||
}
|
}
|
||||||
if (!similar.isInitialized) {
|
if (!similar.isInitialized) {
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
|
|
@ -255,7 +255,11 @@ class SeriesViewModel
|
||||||
if (listIndex != null) {
|
if (listIndex != null) {
|
||||||
refreshEpisode(itemId, listIndex)
|
refreshEpisode(itemId, listIndex)
|
||||||
} else {
|
} else {
|
||||||
fetchItem(seriesId)
|
val item = fetchItem(seriesId)
|
||||||
|
viewModelScope.launchIO {
|
||||||
|
val people = peopleFavorites.getPeopleFor(item)
|
||||||
|
this@SeriesViewModel.people.setValueOnMain(people)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.genresApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.itemsApi
|
import org.jellyfin.sdk.api.client.extensions.itemsApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.liveTvApi
|
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.playlistsApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.suggestionsApi
|
import org.jellyfin.sdk.api.client.extensions.suggestionsApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.tvShowsApi
|
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.GetGenresRequest
|
||||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||||
import org.jellyfin.sdk.model.api.request.GetNextUpRequest
|
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.GetPlaylistItemsRequest
|
||||||
import org.jellyfin.sdk.model.api.request.GetResumeItemsRequest
|
import org.jellyfin.sdk.model.api.request.GetResumeItemsRequest
|
||||||
import org.jellyfin.sdk.model.api.request.GetSuggestionsRequest
|
import org.jellyfin.sdk.model.api.request.GetSuggestionsRequest
|
||||||
|
|
@ -365,3 +367,22 @@ val GetProgramsDtoHandler =
|
||||||
request: GetProgramsDto,
|
request: GetProgramsDto,
|
||||||
): Response<BaseItemDtoQueryResult> = api.liveTvApi.getPrograms(request)
|
): 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))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.github.damontecres.wholphin.util
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -82,6 +82,7 @@
|
||||||
<string name="playback_speed">Playback Speed</string>
|
<string name="playback_speed">Playback Speed</string>
|
||||||
<string name="playback">Playback</string>
|
<string name="playback">Playback</string>
|
||||||
<string name="playlist">Playlist</string>
|
<string name="playlist">Playlist</string>
|
||||||
|
<string name="playlists">Playlists</string>
|
||||||
<string name="preview">Preview</string>
|
<string name="preview">Preview</string>
|
||||||
<string name="profile_specific_settings">User Profile Settings</string>
|
<string name="profile_specific_settings">User Profile Settings</string>
|
||||||
<string name="queue">Queue</string>
|
<string name="queue">Queue</string>
|
||||||
|
|
@ -135,6 +136,7 @@
|
||||||
<string name="version">Version</string>
|
<string name="version">Version</string>
|
||||||
<string name="video_scale">Video Scale</string>
|
<string name="video_scale">Video Scale</string>
|
||||||
<string name="video">Video</string>
|
<string name="video">Video</string>
|
||||||
|
<string name="videos">Videos</string>
|
||||||
<string name="watch_live">Watch live</string>
|
<string name="watch_live">Watch live</string>
|
||||||
<string name="years_old">%1$d years old</string>
|
<string name="years_old">%1$d years old</string>
|
||||||
<string name="play_with_transcoding">Play with transcoding</string>
|
<string name="play_with_transcoding">Play with transcoding</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue