Fix favorites not loading (#156)

Fixes bug introduced in #150 that prevented the favorites page from
loading anything

Also remembers the sort for each favorites tab
This commit is contained in:
damontecres 2025-11-04 13:33:03 -05:00 committed by GitHub
parent a0b9ac7673
commit 13c2bb32d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 110 additions and 50 deletions

View file

@ -6,6 +6,7 @@ import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.github.damontecres.wholphin.data.model.JellyfinUser
import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
import com.github.damontecres.wholphin.ui.toServerString
import java.util.UUID
@Dao
@ -13,12 +14,17 @@ interface LibraryDisplayInfoDao {
fun getItem(
user: JellyfinUser,
itemId: UUID,
): LibraryDisplayInfo? = getItem(user.rowId, itemId.toServerString())
fun getItem(
user: JellyfinUser,
itemId: String,
): LibraryDisplayInfo? = getItem(user.rowId, itemId)
@Query("SELECT * from LibraryDisplayInfo WHERE userId=:userId AND itemId=:itemId")
fun getItem(
userId: Int,
itemId: UUID,
itemId: String,
): LibraryDisplayInfo?
@Insert(onConflict = OnConflictStrategy.REPLACE)

View file

@ -7,6 +7,7 @@ import androidx.room.ForeignKey
import androidx.room.Ignore
import androidx.room.Index
import com.github.damontecres.wholphin.ui.data.SortAndDirection
import com.github.damontecres.wholphin.ui.toServerString
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.UseSerializers
@ -31,10 +32,17 @@ import java.util.UUID
@Serializable
data class LibraryDisplayInfo(
val userId: Int,
val itemId: UUID,
val itemId: String,
val sort: ItemSortBy,
val direction: SortOrder,
) {
constructor(
userId: Int,
itemId: UUID,
sort: ItemSortBy,
direction: SortOrder,
) : this(userId, itemId.toServerString(), sort, direction)
@Ignore @Transient
val sortAndDirection = SortAndDirection(sort, direction)
}

View file

@ -37,12 +37,10 @@ import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
import com.github.damontecres.wholphin.ui.SlimItemFields
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.detail.CardGrid
import com.github.damontecres.wholphin.ui.detail.ItemViewModel
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.GetItemsRequestHandler
@ -61,6 +59,7 @@ import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.api.ItemSortBy
import org.jellyfin.sdk.model.api.SortOrder
import org.jellyfin.sdk.model.api.request.GetItemsRequest
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
import java.util.UUID
import javax.inject.Inject
@ -79,8 +78,7 @@ class CollectionFolderViewModel
val filter = MutableLiveData<GetItemsFilter>(GetItemsFilter())
fun init(
itemId: UUID?,
potential: BaseItem?,
itemId: String,
initialSortAndDirection: SortAndDirection?,
recursive: Boolean,
filter: GetItemsFilter,
@ -91,19 +89,21 @@ class CollectionFolderViewModel
context.getString(R.string.error_loading_collection, itemId),
) + Dispatchers.IO,
) {
if (itemId != null) {
fetchItem(itemId)
val sortAndDirection =
if (initialSortAndDirection == null) {
serverRepository.currentUser?.let { user ->
libraryDisplayInfoDao.getItem(user, itemId)?.sortAndDirection
} ?: SortAndDirection.DEFAULT
} else {
SortAndDirection.DEFAULT
}
loadResults(sortAndDirection, recursive, filter)
this@CollectionFolderViewModel.itemId = itemId
itemId?.toUUIDOrNull()?.let {
fetchItem(it)
}
val sortAndDirection =
if (initialSortAndDirection == null) {
serverRepository.currentUser?.let { user ->
libraryDisplayInfoDao.getItem(user, itemId)?.sortAndDirection
} ?: SortAndDirection.DEFAULT
} else {
SortAndDirection.DEFAULT
}
loadResults(sortAndDirection, recursive, filter)
}
fun onSortChange(
@ -238,11 +238,36 @@ class CollectionFolderViewModel
@Composable
fun CollectionFolderGrid(
preferences: UserPreferences,
itemId: UUID?,
item: BaseItem?,
itemId: UUID,
initialFilter: GetItemsFilter,
recursive: Boolean,
onClickItem: (BaseItem) -> Unit,
sortOptions: List<ItemSortBy>,
modifier: Modifier = Modifier,
initialSortAndDirection: SortAndDirection? = null,
showTitle: Boolean = true,
positionCallback: ((columns: Int, position: Int) -> Unit)? = null,
) = CollectionFolderGrid(
preferences,
itemId.toServerString(),
initialFilter,
recursive,
onClickItem,
sortOptions,
modifier,
initialSortAndDirection = initialSortAndDirection,
showTitle = showTitle,
positionCallback = positionCallback,
)
@Composable
fun CollectionFolderGrid(
preferences: UserPreferences,
itemId: String,
initialFilter: GetItemsFilter,
recursive: Boolean,
onClickItem: (BaseItem) -> Unit,
sortOptions: List<ItemSortBy>,
modifier: Modifier = Modifier,
viewModel: CollectionFolderViewModel = hiltViewModel(),
initialSortAndDirection: SortAndDirection? = null,
@ -250,7 +275,7 @@ fun CollectionFolderGrid(
positionCallback: ((columns: Int, position: Int) -> Unit)? = null,
) {
OneTimeLaunchedEffect {
viewModel.init(itemId, item, initialSortAndDirection, recursive, initialFilter)
viewModel.init(itemId, initialSortAndDirection, recursive, initialFilter)
}
val sortAndDirection by viewModel.sortAndDirection.observeAsState()
val filter by viewModel.filter.observeAsState(initialFilter)
@ -276,6 +301,7 @@ fun CollectionFolderGrid(
viewModel.onSortChange(it, recursive, filter)
},
showTitle = showTitle,
sortOptions = sortOptions,
positionCallback = positionCallback,
letterPosition = { viewModel.positionOfLetter(it) ?: -1 },
)
@ -293,18 +319,12 @@ fun CollectionFolderGridContent(
onClickItem: (BaseItem) -> Unit,
onSortChange: (SortAndDirection) -> Unit,
letterPosition: suspend (Char) -> Int,
sortOptions: List<ItemSortBy>,
modifier: Modifier = Modifier,
showTitle: Boolean = true,
positionCallback: ((columns: Int, position: Int) -> Unit)? = null,
) {
val title = item?.name ?: item?.data?.collectionType?.name ?: stringResource(R.string.collection)
val sortOptions =
when (item?.data?.collectionType) {
CollectionType.MOVIES -> MovieSortOptions
CollectionType.TVSHOWS -> SeriesSortOptions
CollectionType.HOMEVIDEOS -> VideoSortOptions
else -> listOf(ItemSortBy.SORT_NAME, ItemSortBy.DATE_CREATED, ItemSortBy.RANDOM)
}
var showHeader by rememberSaveable { mutableStateOf(true) }

View file

@ -45,6 +45,15 @@ val SeriesSortOptions =
ItemSortBy.RANDOM,
)
val EpisodeSortOptions =
listOf(
ItemSortBy.SORT_NAME,
ItemSortBy.DATE_CREATED,
ItemSortBy.DATE_PLAYED,
ItemSortBy.AIRED_EPISODE_ORDER,
ItemSortBy.RANDOM,
)
val VideoSortOptions =
listOf(
ItemSortBy.SORT_NAME,
@ -56,14 +65,19 @@ val VideoSortOptions =
@StringRes
fun getStringRes(sort: ItemSortBy): Int =
when (sort) {
ItemSortBy.SORT_NAME -> R.string.sort_by_name
ItemSortBy.SORT_NAME,
ItemSortBy.SERIES_SORT_NAME,
-> R.string.sort_by_name
ItemSortBy.PREMIERE_DATE -> R.string.sort_by_date_released
ItemSortBy.DATE_CREATED -> R.string.sort_by_date_added
ItemSortBy.DATE_LAST_CONTENT_ADDED -> R.string.sort_by_date_episode_added
ItemSortBy.DATE_PLAYED -> R.string.sort_by_date_played
ItemSortBy.DATE_PLAYED,
ItemSortBy.SERIES_DATE_PLAYED,
-> R.string.sort_by_date_played
ItemSortBy.RANDOM -> R.string.sort_by_random
ItemSortBy.COMMUNITY_RATING -> R.string.community_rating
ItemSortBy.CRITIC_RATING -> R.string.critic_rating
ItemSortBy.PLAY_COUNT -> R.string.play_count
ItemSortBy.AIRED_EPISODE_ORDER -> R.string.aired_episode_order
else -> throw IllegalArgumentException("Unsupported sort option: $sort")
}

View file

@ -13,6 +13,7 @@ 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.ui.components.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.data.VideoSortOptions
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
import java.util.UUID
@ -31,10 +32,10 @@ fun CollectionFolderGeneric(
preferences = preferences,
onClickItem = { preferencesViewModel.navigationManager.navigateTo(it.destination()) },
itemId = itemId,
item = item,
initialFilter = filter,
showTitle = showHeader,
recursive = recursive,
sortOptions = VideoSortOptions,
modifier =
modifier
.padding(start = 16.dp),

View file

@ -41,6 +41,7 @@ import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.data.VideoSortOptions
import com.github.damontecres.wholphin.ui.detail.livetv.DvrSchedule
import com.github.damontecres.wholphin.ui.detail.livetv.TvGuideGrid
import com.github.damontecres.wholphin.ui.ifElse
@ -204,10 +205,10 @@ fun CollectionFolderLiveTv(
preferences = preferences,
onClickItem = onClickItem,
itemId = folders[folderIndex].id,
item = null,
initialFilter = GetItemsFilter(),
showTitle = showHeader,
recursive = false,
sortOptions = VideoSortOptions,
modifier =
Modifier
.padding(start = 16.dp),

View file

@ -38,6 +38,8 @@ import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.GenreCardGrid
import com.github.damontecres.wholphin.ui.components.RecommendedMovie
import com.github.damontecres.wholphin.ui.data.MovieSortOptions
import com.github.damontecres.wholphin.ui.data.VideoSortOptions
import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
@ -160,13 +162,13 @@ fun CollectionFolderMovie(
preferences = preferences,
onClickItem = onClickItem,
itemId = destination.itemId,
item = destination.item,
initialFilter =
GetItemsFilter(
includeItemTypes = listOf(BaseItemKind.MOVIE),
),
showTitle = false,
recursive = true,
sortOptions = MovieSortOptions,
modifier =
Modifier
.padding(start = 16.dp)
@ -182,13 +184,13 @@ fun CollectionFolderMovie(
preferences = preferences,
onClickItem = onClickItem,
itemId = destination.itemId,
item = destination.item,
initialFilter =
GetItemsFilter(
includeItemTypes = listOf(BaseItemKind.BOX_SET),
),
showTitle = false,
recursive = true,
sortOptions = VideoSortOptions,
modifier =
Modifier
.padding(start = 16.dp)

View file

@ -38,6 +38,7 @@ import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.GenreCardGrid
import com.github.damontecres.wholphin.ui.components.RecommendedTvShow
import com.github.damontecres.wholphin.ui.data.SeriesSortOptions
import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
@ -158,13 +159,13 @@ fun CollectionFolderTv(
CollectionFolderGrid(
preferences = preferences,
itemId = destination.itemId,
item = destination.item,
initialFilter =
GetItemsFilter(
includeItemTypes = listOf(BaseItemKind.SERIES),
),
showTitle = false,
recursive = true,
sortOptions = SeriesSortOptions,
modifier =
Modifier
.padding(start = 16.dp)

View file

@ -36,6 +36,9 @@ import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.ErrorMessage
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.nav.NavDrawerItem
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
import com.github.damontecres.wholphin.ui.tryRequestFocus
@ -147,8 +150,7 @@ fun FavoritesPage(
CollectionFolderGrid(
preferences = preferences,
onClickItem = onClickItem,
itemId = null,
item = null,
itemId = "${NavDrawerItem.Favorites.id}_movies",
initialFilter =
GetItemsFilter(
favorite = true,
@ -156,6 +158,7 @@ fun FavoritesPage(
),
showTitle = false,
recursive = true,
sortOptions = MovieSortOptions,
modifier =
Modifier
.padding(start = 16.dp)
@ -170,8 +173,7 @@ fun FavoritesPage(
CollectionFolderGrid(
preferences = preferences,
onClickItem = onClickItem,
itemId = null,
item = null,
itemId = "${NavDrawerItem.Favorites.id}_series",
initialFilter =
GetItemsFilter(
favorite = true,
@ -179,6 +181,7 @@ fun FavoritesPage(
),
showTitle = false,
recursive = true,
sortOptions = SeriesSortOptions,
modifier =
Modifier
.padding(start = 16.dp)
@ -193,8 +196,7 @@ fun FavoritesPage(
CollectionFolderGrid(
preferences = preferences,
onClickItem = onClickItem,
itemId = null,
item = null,
itemId = "${NavDrawerItem.Favorites.id}_episodes",
initialFilter =
GetItemsFilter(
favorite = true,
@ -202,6 +204,7 @@ fun FavoritesPage(
),
showTitle = false,
recursive = true,
sortOptions = EpisodeSortOptions,
modifier =
Modifier
.padding(start = 16.dp)

View file

@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.ui.setValueOnMain
import com.github.damontecres.wholphin.ui.toServerString
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState
import kotlinx.coroutines.Deferred
@ -25,11 +26,13 @@ abstract class ItemViewModel(
val api: ApiClient,
) : ViewModel() {
val item = MutableLiveData<BaseItem?>(null)
lateinit var itemId: UUID
lateinit var itemId: String
lateinit var itemUuid: UUID
suspend fun fetchItem(itemId: UUID): BaseItem =
withContext(Dispatchers.IO) {
this@ItemViewModel.itemId = itemId
this@ItemViewModel.itemId = itemId.toServerString()
this@ItemViewModel.itemUuid = itemId
val it = api.userLibraryApi.getItem(itemId).content
val fetchedItem = BaseItem.from(it, api)
return@withContext fetchedItem.let {

View file

@ -129,21 +129,21 @@ class MovieViewModel
fun setWatched(played: Boolean) =
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
if (played) {
api.playStateApi.markPlayedItem(itemId)
api.playStateApi.markPlayedItem(itemUuid)
} else {
api.playStateApi.markUnplayedItem(itemId)
api.playStateApi.markUnplayedItem(itemUuid)
}
fetchAndSetItem(itemId)
fetchAndSetItem(itemUuid)
}
fun setFavorite(favorite: Boolean) =
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
if (favorite) {
api.userLibraryApi.markFavoriteItem(itemId)
api.userLibraryApi.markFavoriteItem(itemUuid)
} else {
api.userLibraryApi.unmarkFavoriteItem(itemId)
api.userLibraryApi.unmarkFavoriteItem(itemUuid)
}
fetchAndSetItem(itemId)
fetchAndSetItem(itemUuid)
}
fun savePlayVersion(

View file

@ -138,6 +138,7 @@
<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>
<string name="aired_episode_order">Aired Episode Order</string>
<plurals name="downloads">
<item quantity="zero">%d downloads</item>