feat: mixed libraries (#628)

* mixed collection

* Mixed libraries to show up in "Latest"

Co-authored-by: Freya Winters <freya@justgamingtld.nl>

* Do not recurse if folders are shown

* Added folder navigation for tv

* Removed assumption that folder == mixed

* refactor: add default values to `FindroidFolder`

* fix: add chapters to findroidfolder

---------

Co-authored-by: Freya Winters <freya@justgamingtld.nl>
Co-authored-by: Jarne Demeulemeester <jarnedemeulemeester@gmail.com>
This commit is contained in:
Nelson Wu 2024-02-25 03:48:03 +11:00 committed by GitHub
parent 4ec3b2e40c
commit 9cd3295d2d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 64 additions and 4 deletions

View file

@ -25,6 +25,7 @@ import dev.jdtech.jellyfin.databinding.FragmentLibraryBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.dialogs.SortDialogFragment import dev.jdtech.jellyfin.dialogs.SortDialogFragment
import dev.jdtech.jellyfin.models.FindroidBoxSet import dev.jdtech.jellyfin.models.FindroidBoxSet
import dev.jdtech.jellyfin.models.FindroidFolder
import dev.jdtech.jellyfin.models.FindroidItem import dev.jdtech.jellyfin.models.FindroidItem
import dev.jdtech.jellyfin.models.FindroidMovie import dev.jdtech.jellyfin.models.FindroidMovie
import dev.jdtech.jellyfin.models.FindroidShow import dev.jdtech.jellyfin.models.FindroidShow
@ -222,6 +223,15 @@ class LibraryFragment : Fragment() {
), ),
) )
} }
is FindroidFolder -> {
findNavController().navigate(
LibraryFragmentDirections.actionLibraryFragmentSelf(
item.id,
item.name,
args.libraryType,
),
)
}
} }
} }
} }

View file

@ -123,6 +123,9 @@
<argument <argument
android:name="libraryType" android:name="libraryType"
app:argType="dev.jdtech.jellyfin.models.CollectionType" /> app:argType="dev.jdtech.jellyfin.models.CollectionType" />
<action
android:id="@+id/action_libraryFragment_self"
app:destination="@id/libraryFragment" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/showFragment" android:id="@+id/showFragment"

View file

@ -23,9 +23,11 @@ import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text import androidx.tv.material3.Text
import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import dev.jdtech.jellyfin.destinations.LibraryScreenDestination
import dev.jdtech.jellyfin.destinations.MovieScreenDestination import dev.jdtech.jellyfin.destinations.MovieScreenDestination
import dev.jdtech.jellyfin.destinations.ShowScreenDestination import dev.jdtech.jellyfin.destinations.ShowScreenDestination
import dev.jdtech.jellyfin.models.CollectionType import dev.jdtech.jellyfin.models.CollectionType
import dev.jdtech.jellyfin.models.FindroidFolder
import dev.jdtech.jellyfin.models.FindroidItem import dev.jdtech.jellyfin.models.FindroidItem
import dev.jdtech.jellyfin.models.FindroidMovie import dev.jdtech.jellyfin.models.FindroidMovie
import dev.jdtech.jellyfin.models.FindroidShow import dev.jdtech.jellyfin.models.FindroidShow
@ -65,6 +67,9 @@ fun LibraryScreen(
is FindroidShow -> { is FindroidShow -> {
navigator.navigate(ShowScreenDestination(item.id)) navigator.navigate(ShowScreenDestination(item.id))
} }
is FindroidFolder -> {
navigator.navigate(LibraryScreenDestination(item.id, item.name, libraryType))
}
} }
}, },
) )

View file

@ -93,7 +93,7 @@ class HomeViewModel @Inject internal constructor(
private suspend fun loadViews() = repository private suspend fun loadViews() = repository
.getUserViews() .getUserViews()
.filter { view -> CollectionType.supported.any { it.type == view.collectionType } } .filter { view -> CollectionType.fromString(view.collectionType) in CollectionType.supported }
.map { view -> view to repository.getLatestMedia(view.id) } .map { view -> view to repository.getLatestMedia(view.id) }
.filter { (_, latest) -> latest.isNotEmpty() } .filter { (_, latest) -> latest.isNotEmpty() }
.map { (view, latest) -> view.toView().apply { items = latest } } .map { (view, latest) -> view.toView().apply { items = latest } }

View file

@ -48,15 +48,19 @@ constructor(
CollectionType.Movies -> listOf(BaseItemKind.MOVIE) CollectionType.Movies -> listOf(BaseItemKind.MOVIE)
CollectionType.TvShows -> listOf(BaseItemKind.SERIES) CollectionType.TvShows -> listOf(BaseItemKind.SERIES)
CollectionType.BoxSets -> listOf(BaseItemKind.BOX_SET) CollectionType.BoxSets -> listOf(BaseItemKind.BOX_SET)
CollectionType.Mixed -> listOf(BaseItemKind.FOLDER, BaseItemKind.MOVIE, BaseItemKind.SERIES)
else -> null else -> null
} }
val recursive = itemType == null || !itemType.contains(BaseItemKind.FOLDER)
viewModelScope.launch { viewModelScope.launch {
_uiState.emit(UiState.Loading) _uiState.emit(UiState.Loading)
try { try {
val items = jellyfinRepository.getItemsPaging( val items = jellyfinRepository.getItemsPaging(
parentId = parentId, parentId = parentId,
includeTypes = itemType, includeTypes = itemType,
recursive = true, recursive = recursive,
sortBy = sortBy, sortBy = sortBy,
sortOrder = sortOrder, sortOrder = sortOrder,
).cachedIn(viewModelScope) ).cachedIn(viewModelScope)

View file

@ -9,6 +9,7 @@ enum class CollectionType(val type: String) {
Books("books"), Books("books"),
LiveTv("livetv"), LiveTv("livetv"),
BoxSets("boxsets"), BoxSets("boxsets"),
Mixed("null"),
Unknown("unknown"), Unknown("unknown"),
; ;
@ -19,11 +20,12 @@ enum class CollectionType(val type: String) {
Movies, Movies,
TvShows, TvShows,
BoxSets, BoxSets,
Mixed,
) )
fun fromString(string: String?): CollectionType { fun fromString(string: String?): CollectionType {
if (string == null) { if (string == null) { // TODO jellyfin returns null as the collectiontype for mixed libraries. This is obviously wrong, but probably an upstream issue. Should be fixed whenever upstream fixes this
return defaultValue return Mixed
} }
return try { return try {

View file

@ -0,0 +1,35 @@
package dev.jdtech.jellyfin.models
import dev.jdtech.jellyfin.repository.JellyfinRepository
import org.jellyfin.sdk.model.api.BaseItemDto
import java.util.UUID
data class FindroidFolder(
override val id: UUID,
override val name: String,
override val originalTitle: String? = null,
override val overview: String = "",
override val played: Boolean,
override val favorite: Boolean,
override val canPlay: Boolean = false,
override val canDownload: Boolean = false,
override val sources: List<FindroidSource> = emptyList(),
override val runtimeTicks: Long = 0L,
override val playbackPositionTicks: Long = 0L,
override val unplayedItemCount: Int?,
override val images: FindroidImages,
override val chapters: List<FindroidChapter>? = null,
) : FindroidItem
fun BaseItemDto.toFindroidFolder(
jellyfinRepository: JellyfinRepository,
): FindroidFolder {
return FindroidFolder(
id = id,
name = name.orEmpty(),
played = userData?.played ?: false,
favorite = userData?.isFavorite ?: false,
unplayedItemCount = userData?.unplayedItemCount,
images = toFindroidImages(jellyfinRepository),
)
}

View file

@ -33,6 +33,7 @@ suspend fun BaseItemDto.toFindroidItem(
BaseItemKind.SEASON -> toFindroidSeason(jellyfinRepository) BaseItemKind.SEASON -> toFindroidSeason(jellyfinRepository)
BaseItemKind.SERIES -> toFindroidShow(jellyfinRepository) BaseItemKind.SERIES -> toFindroidShow(jellyfinRepository)
BaseItemKind.BOX_SET -> toFindroidBoxSet(jellyfinRepository) BaseItemKind.BOX_SET -> toFindroidBoxSet(jellyfinRepository)
BaseItemKind.FOLDER -> toFindroidFolder(jellyfinRepository)
else -> null else -> null
} }
} }