WIP Playlist updates

This commit is contained in:
Damontecres 2026-03-06 17:51:42 -05:00
parent 0bb91f2438
commit ec43546bad
No known key found for this signature in database

View file

@ -82,6 +82,7 @@ import com.github.damontecres.wholphin.ui.data.SortAndDirection
import com.github.damontecres.wholphin.ui.enableMarquee import com.github.damontecres.wholphin.ui.enableMarquee
import com.github.damontecres.wholphin.ui.formatDateTime import com.github.damontecres.wholphin.ui.formatDateTime
import com.github.damontecres.wholphin.ui.ifElse import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.launchDefault
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.roundMinutes import com.github.damontecres.wholphin.ui.roundMinutes
@ -102,6 +103,7 @@ import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
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.ItemSortBy
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.extensions.ticks import org.jellyfin.sdk.model.extensions.ticks
@ -135,6 +137,7 @@ class PlaylistViewModel
), ),
) )
val musicState = musicService.state val musicState = musicService.state
val playlistMediaType = MutableStateFlow(MediaType.UNKNOWN)
fun init(playlistId: UUID) { fun init(playlistId: UUID) {
loading.value = LoadingState.Loading loading.value = LoadingState.Loading
@ -153,75 +156,112 @@ class PlaylistViewModel
ItemSortBy.DEFAULT, ItemSortBy.DEFAULT,
SortOrder.ASCENDING, SortOrder.ASCENDING,
) )
loadItems(filter, sortAndDirection) loadItems(filter, sortAndDirection).join()
determineMediaType()
} }
} }
fun loadItems( fun loadItems(
filter: GetItemsFilter, filter: GetItemsFilter,
sortAndDirection: SortAndDirection, sortAndDirection: SortAndDirection,
) { ) = viewModelScope.launchIO {
viewModelScope.launchIO { backdropService.clearBackdrop()
backdropService.clearBackdrop() loading.setValueOnMain(LoadingState.Loading)
loading.setValueOnMain(LoadingState.Loading) this@PlaylistViewModel.filterAndSort.update {
this@PlaylistViewModel.filterAndSort.update { FilterAndSort(filter, sortAndDirection)
FilterAndSort(filter, sortAndDirection) }
}
serverRepository.currentUser.value?.let { user -> serverRepository.currentUser.value?.let { user ->
val playlistId = item.value!!.id val playlistId = item.value!!.id
viewModelScope.launchIO { viewModelScope.launchIO {
val libraryDisplayInfo = val libraryDisplayInfo =
libraryDisplayInfoDao.getItem(user, itemId)?.copy( libraryDisplayInfoDao.getItem(user, itemId)?.copy(
filter = filter, filter = filter,
sort = sortAndDirection.sort,
direction = sortAndDirection.direction,
)
?: LibraryDisplayInfo(
userId = user.rowId,
itemId = itemId,
sort = sortAndDirection.sort, sort = sortAndDirection.sort,
direction = sortAndDirection.direction, direction = sortAndDirection.direction,
filter = filter,
viewOptions = null,
) )
?: LibraryDisplayInfo( libraryDisplayInfoDao.saveItem(libraryDisplayInfo)
userId = user.rowId, }
itemId = itemId,
sort = sortAndDirection.sort, val request =
direction = sortAndDirection.direction, filter.applyTo(
filter = filter, GetItemsRequest(
viewOptions = null, parentId = playlistId,
) userId = user.id,
libraryDisplayInfoDao.saveItem(libraryDisplayInfo) fields = DefaultItemFields,
sortBy = listOf(sortAndDirection.sort),
sortOrder = listOf(sortAndDirection.direction),
),
)
try {
val pager =
ApiRequestPager(
api,
request,
GetItemsRequestHandler,
viewModelScope,
).init()
withContext(Dispatchers.Main) {
items.value = pager
loading.value = LoadingState.Success
} }
} catch (ex: Exception) {
val request = Timber.e(ex, "Error fetching playlist %s", itemId)
filter.applyTo( withContext(Dispatchers.Main) {
GetItemsRequest( items.value = listOf()
parentId = playlistId, loading.value = LoadingState.Error(ex)
userId = user.id,
fields = DefaultItemFields,
sortBy = listOf(sortAndDirection.sort),
sortOrder = listOf(sortAndDirection.direction),
),
)
try {
val pager =
ApiRequestPager(
api,
request,
GetItemsRequestHandler,
viewModelScope,
).init()
withContext(Dispatchers.Main) {
items.value = pager
loading.value = LoadingState.Success
}
} catch (ex: Exception) {
Timber.e(ex, "Error fetching playlist %s", itemId)
withContext(Dispatchers.Main) {
items.value = listOf()
loading.value = LoadingState.Error(ex)
}
} }
} }
} }
} }
private suspend fun determineMediaType() {
var mediaType =
super.item.value
?.data
?.mediaType ?: MediaType.UNKNOWN
mediaType =
if (mediaType == MediaType.UNKNOWN) {
val pager = (this@PlaylistViewModel.items.value as? ApiRequestPager<*>)
if (pager != null && pager.size < 50) {
val types =
(0..<50).groupBy { index ->
val pagerItem = pager.getBlocking(index)
when (pagerItem?.type) {
BaseItemKind.AUDIO -> MediaType.AUDIO
BaseItemKind.VIDEO,
BaseItemKind.EPISODE,
BaseItemKind.MOVIE,
BaseItemKind.BOX_SET,
-> MediaType.VIDEO
else -> MediaType.UNKNOWN
}
}
if (types.keys.size == 1) {
types.keys.first()
} else {
MediaType.UNKNOWN
}
} else {
MediaType.UNKNOWN
}
} else {
mediaType
}
playlistMediaType.update { mediaType }
}
suspend fun getFilterOptionValues(filterOption: ItemFilterBy<*>): List<FilterValueOption> = suspend fun getFilterOptionValues(filterOption: ItemFilterBy<*>): List<FilterValueOption> =
FilterUtils.getFilterOptionValues( FilterUtils.getFilterOptionValues(
api, api,
@ -235,6 +275,17 @@ class PlaylistViewModel
backdropService.submit(item) backdropService.submit(item)
} }
} }
fun playMusic(
index: Int,
shuffle: Boolean,
) {
viewModelScope.launchDefault {
(items.value as? ApiRequestPager<*>)?.let {
musicService.setQueue(it, index, shuffle)
}
}
}
} }
@Immutable @Immutable
@ -258,39 +309,53 @@ fun PlaylistDetails(
val items by viewModel.items.observeAsState(listOf()) val items by viewModel.items.observeAsState(listOf())
val filterAndSort by viewModel.filterAndSort.collectAsState() val filterAndSort by viewModel.filterAndSort.collectAsState()
val musicState by viewModel.musicState.collectAsState() val musicState by viewModel.musicState.collectAsState()
val playlistMediaType by viewModel.playlistMediaType.collectAsState()
var longClickDialog by remember { mutableStateOf<DialogParams?>(null) } var longClickDialog by remember { mutableStateOf<DialogParams?>(null) }
var showConfirmTypeDialog by remember { mutableStateOf(false) }
val goToString = stringResource(R.string.go_to) val goToString = stringResource(R.string.go_to)
val playFromHereString = stringResource(R.string.play_from_here) val playFromHereString = stringResource(R.string.play_from_here)
fun play(
index: Int,
shuffle: Boolean,
mediaTypeOverride: MediaType? = null,
) {
when (mediaTypeOverride ?: playlistMediaType) {
MediaType.VIDEO -> {
viewModel.navigationManager.navigateTo(
Destination.PlaybackList(
itemId = destination.itemId,
startIndex = index,
shuffle = shuffle,
filter = filterAndSort.filter,
sortAndDirection = filterAndSort.sortAndDirection,
),
)
}
MediaType.AUDIO -> {
viewModel.playMusic(index, shuffle)
}
else -> {
showConfirmTypeDialog = true
}
}
}
PlaylistDetailsContent( PlaylistDetailsContent(
loadingState = loading, loadingState = loading,
playlist = playlist, playlist = playlist,
items = items, items = items,
musicState = musicState, musicState = musicState,
onChangeBackdrop = viewModel::updateBackdrop, onChangeBackdrop = viewModel::updateBackdrop,
onClickIndex = { index, _ -> onClickIndex = { index, item ->
viewModel.navigationManager.navigateTo( play(index, false)
Destination.PlaybackList(
itemId = destination.itemId,
startIndex = index,
shuffle = false,
filter = filterAndSort.filter,
sortAndDirection = filterAndSort.sortAndDirection,
),
)
}, },
onClickPlay = { shuffle -> onClickPlay = { shuffle ->
viewModel.navigationManager.navigateTo( play(0, shuffle)
Destination.PlaybackList(
itemId = destination.itemId,
startIndex = 0,
shuffle = shuffle,
filter = filterAndSort.filter,
sortAndDirection = filterAndSort.sortAndDirection,
),
)
}, },
onLongClickIndex = { index, item -> onLongClickIndex = { index, item ->
longClickDialog = longClickDialog =
@ -309,15 +374,7 @@ fun PlaylistDetails(
playFromHereString, playFromHereString,
Icons.Default.PlayArrow, Icons.Default.PlayArrow,
) { ) {
viewModel.navigationManager.navigateTo( play(index, false)
Destination.PlaybackList(
itemId = destination.itemId,
startIndex = index,
shuffle = false,
filter = filterAndSort.filter,
sortAndDirection = filterAndSort.sortAndDirection,
),
)
}, },
), ),
) )