mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 08:01:20 +02:00
Fixes to genre grids (#693)
## Description Use only TV series genres for a TV library's genre tab. This ensures only series are shown, not episodes, which removes some of the seemingly empty genres. Also fixes the image filtering to ensure an item with a backdrop is always used if available. ### Related issues Fixes #689 Fixes #690
This commit is contained in:
parent
18e4877736
commit
c4e64c3367
4 changed files with 40 additions and 26 deletions
|
|
@ -30,6 +30,7 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
@ -783,12 +784,15 @@ fun CollectionFolderGridContent(
|
||||||
var showHeader by rememberSaveable { mutableStateOf(true) }
|
var showHeader by rememberSaveable { mutableStateOf(true) }
|
||||||
var showViewOptions by rememberSaveable { mutableStateOf(false) }
|
var showViewOptions by rememberSaveable { mutableStateOf(false) }
|
||||||
var viewOptions by remember { mutableStateOf(viewOptions) }
|
var viewOptions by remember { mutableStateOf(viewOptions) }
|
||||||
|
val headerRowFocusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
val gridFocusRequester = remember { FocusRequester() }
|
val gridFocusRequester = remember { FocusRequester() }
|
||||||
if (pager.isNotEmpty()) {
|
if (pager.isNotEmpty()) {
|
||||||
RequestOrRestoreFocus(gridFocusRequester)
|
RequestOrRestoreFocus(gridFocusRequester)
|
||||||
} else {
|
} else {
|
||||||
focusRequesterOnEmpty?.tryRequestFocus()
|
LaunchedEffect(Unit) {
|
||||||
|
(focusRequesterOnEmpty ?: headerRowFocusRequester).tryRequestFocus()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var backdropImageUrl by remember { mutableStateOf<String?>(null) }
|
var backdropImageUrl by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
|
|
@ -833,7 +837,8 @@ fun CollectionFolderGridContent(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(start = 16.dp, end = endPadding)
|
.padding(start = 16.dp, end = endPadding)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth()
|
||||||
|
.focusRequester(headerRowFocusRequester),
|
||||||
) {
|
) {
|
||||||
if (sortOptions.isNotEmpty() || filterOptions.isNotEmpty()) {
|
if (sortOptions.isNotEmpty() || filterOptions.isNotEmpty()) {
|
||||||
Row(
|
Row(
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,9 @@ import com.github.damontecres.wholphin.util.GetGenresRequestHandler
|
||||||
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 dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
|
|
@ -55,29 +58,32 @@ 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 java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel(assistedFactory = GenreViewModel.Factory::class)
|
||||||
class GenreViewModel
|
class GenreViewModel
|
||||||
@Inject
|
@AssistedInject
|
||||||
constructor(
|
constructor(
|
||||||
private val api: ApiClient,
|
private val api: ApiClient,
|
||||||
private val imageUrlService: ImageUrlService,
|
private val imageUrlService: ImageUrlService,
|
||||||
private val serverRepository: ServerRepository,
|
private val serverRepository: ServerRepository,
|
||||||
val navigationManager: NavigationManager,
|
val navigationManager: NavigationManager,
|
||||||
|
@Assisted private val itemId: UUID,
|
||||||
|
@Assisted private val includeItemTypes: List<BaseItemKind>?,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private lateinit var itemId: UUID
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(
|
||||||
|
itemId: UUID,
|
||||||
|
includeItemTypes: List<BaseItemKind>?,
|
||||||
|
): GenreViewModel
|
||||||
|
}
|
||||||
|
|
||||||
val item = MutableLiveData<BaseItem?>(null)
|
val item = MutableLiveData<BaseItem?>(null)
|
||||||
val loading = MutableLiveData<LoadingState>(LoadingState.Pending)
|
val loading = MutableLiveData<LoadingState>(LoadingState.Pending)
|
||||||
val genres = MutableLiveData<List<Genre>>(listOf())
|
val genres = MutableLiveData<List<Genre>>(listOf())
|
||||||
|
|
||||||
fun init(
|
fun init(cardWidthPx: Int) {
|
||||||
itemId: UUID,
|
|
||||||
cardWidthPx: Int,
|
|
||||||
) {
|
|
||||||
loading.value = LoadingState.Loading
|
loading.value = LoadingState.Loading
|
||||||
this.itemId = itemId
|
|
||||||
viewModelScope.launch(Dispatchers.IO + LoadingExceptionHandler(loading, "Failed to fetch genres")) {
|
viewModelScope.launch(Dispatchers.IO + LoadingExceptionHandler(loading, "Failed to fetch genres")) {
|
||||||
val item =
|
val item =
|
||||||
api.userLibraryApi.getItem(itemId = itemId).content.let {
|
api.userLibraryApi.getItem(itemId = itemId).content.let {
|
||||||
|
|
@ -89,6 +95,7 @@ class GenreViewModel
|
||||||
userId = serverRepository.currentUser.value?.id,
|
userId = serverRepository.currentUser.value?.id,
|
||||||
parentId = itemId,
|
parentId = itemId,
|
||||||
fields = SlimItemFields,
|
fields = SlimItemFields,
|
||||||
|
includeItemTypes = includeItemTypes,
|
||||||
)
|
)
|
||||||
val genres =
|
val genres =
|
||||||
GetGenresRequestHandler
|
GetGenresRequestHandler
|
||||||
|
|
@ -97,12 +104,10 @@ class GenreViewModel
|
||||||
.map {
|
.map {
|
||||||
Genre(it.id, it.name ?: "", null, Color.Black)
|
Genre(it.id, it.name ?: "", null, Color.Black)
|
||||||
}
|
}
|
||||||
// val pager = ApiRequestPager(api, request, GetGenresRequestHandler, viewModelScope).init()
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
this@GenreViewModel.genres.value = genres
|
this@GenreViewModel.genres.value = genres
|
||||||
loading.value = LoadingState.Success
|
loading.value = LoadingState.Success
|
||||||
}
|
}
|
||||||
// val excludeItemIds = mutableSetOf<UUID>()
|
|
||||||
val genreToUrl = ConcurrentHashMap<UUID, String?>()
|
val genreToUrl = ConcurrentHashMap<UUID, String?>()
|
||||||
val semaphore = Semaphore(4)
|
val semaphore = Semaphore(4)
|
||||||
genres
|
genres
|
||||||
|
|
@ -114,34 +119,28 @@ class GenreViewModel
|
||||||
.execute(
|
.execute(
|
||||||
api,
|
api,
|
||||||
GetItemsRequest(
|
GetItemsRequest(
|
||||||
// excludeItemIds = excludeItemIds,
|
|
||||||
parentId = itemId,
|
parentId = itemId,
|
||||||
recursive = true,
|
recursive = true,
|
||||||
limit = 1,
|
limit = 1,
|
||||||
sortBy = listOf(ItemSortBy.RANDOM),
|
sortBy = listOf(ItemSortBy.RANDOM),
|
||||||
fields = listOf(ItemFields.GENRES),
|
fields = listOf(ItemFields.GENRES),
|
||||||
imageTypes = listOf(ImageType.THUMB),
|
imageTypes = listOf(ImageType.BACKDROP),
|
||||||
imageTypeLimit = 1,
|
imageTypeLimit = 1,
|
||||||
includeItemTypes =
|
includeItemTypes = includeItemTypes,
|
||||||
listOf(
|
|
||||||
BaseItemKind.MOVIE,
|
|
||||||
BaseItemKind.SERIES,
|
|
||||||
),
|
|
||||||
genreIds = listOf(genre.id),
|
genreIds = listOf(genre.id),
|
||||||
enableTotalRecordCount = false,
|
enableTotalRecordCount = false,
|
||||||
),
|
),
|
||||||
).content.items
|
).content.items
|
||||||
.firstOrNull()
|
.firstOrNull()
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
// excludeItemIds.add(item.id)
|
|
||||||
genreToUrl[genre.id] =
|
genreToUrl[genre.id] =
|
||||||
imageUrlService.getItemImageUrl(
|
imageUrlService.getItemImageUrl(
|
||||||
itemId = item.id,
|
itemId = item.id,
|
||||||
itemType = item.type,
|
itemType = item.type,
|
||||||
seriesId = null,
|
seriesId = null,
|
||||||
useSeriesForPrimary = false,
|
useSeriesForPrimary = true,
|
||||||
imageType = ImageType.BACKDROP,
|
imageType = ImageType.BACKDROP,
|
||||||
imageTags = emptyMap(),
|
imageTags = item.imageTags.orEmpty(),
|
||||||
fillWidth = cardWidthPx,
|
fillWidth = cardWidthPx,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -186,8 +185,12 @@ data class Genre(
|
||||||
@Composable
|
@Composable
|
||||||
fun GenreCardGrid(
|
fun GenreCardGrid(
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
|
includeItemTypes: List<BaseItemKind>?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: GenreViewModel = hiltViewModel(),
|
viewModel: GenreViewModel =
|
||||||
|
hiltViewModel<GenreViewModel, GenreViewModel.Factory>(
|
||||||
|
creationCallback = { it.create(itemId, includeItemTypes) },
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
val columns = 4
|
val columns = 4
|
||||||
val spacing = 16.dp
|
val spacing = 16.dp
|
||||||
|
|
@ -205,7 +208,7 @@ fun GenreCardGrid(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
OneTimeLaunchedEffect {
|
OneTimeLaunchedEffect {
|
||||||
viewModel.init(itemId, cardWidthPx)
|
viewModel.init(cardWidthPx)
|
||||||
}
|
}
|
||||||
val loading by viewModel.loading.observeAsState(LoadingState.Pending)
|
val loading by viewModel.loading.observeAsState(LoadingState.Pending)
|
||||||
val genres by viewModel.genres.observeAsState(listOf())
|
val genres by viewModel.genres.observeAsState(listOf())
|
||||||
|
|
@ -239,7 +242,11 @@ fun GenreCardGrid(
|
||||||
genre.name,
|
genre.name,
|
||||||
item?.title,
|
item?.title,
|
||||||
).joinToString(" "),
|
).joinToString(" "),
|
||||||
filter = GetItemsFilter(genres = listOf(genre.id)),
|
filter =
|
||||||
|
GetItemsFilter(
|
||||||
|
genres = listOf(genre.id),
|
||||||
|
includeItemTypes = includeItemTypes,
|
||||||
|
),
|
||||||
useSavedLibraryDisplayInfo = false,
|
useSavedLibraryDisplayInfo = false,
|
||||||
),
|
),
|
||||||
recursive = true,
|
recursive = true,
|
||||||
|
|
|
||||||
|
|
@ -179,6 +179,7 @@ fun CollectionFolderMovie(
|
||||||
3 -> {
|
3 -> {
|
||||||
GenreCardGrid(
|
GenreCardGrid(
|
||||||
itemId = destination.itemId,
|
itemId = destination.itemId,
|
||||||
|
includeItemTypes = listOf(BaseItemKind.MOVIE),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(start = 16.dp)
|
.padding(start = 16.dp)
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,7 @@ fun CollectionFolderTv(
|
||||||
2 -> {
|
2 -> {
|
||||||
GenreCardGrid(
|
GenreCardGrid(
|
||||||
itemId = destination.itemId,
|
itemId = destination.itemId,
|
||||||
|
includeItemTypes = listOf(BaseItemKind.SERIES),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(start = 16.dp)
|
.padding(start = 16.dp)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue