More options for movies

This commit is contained in:
Damontecres 2025-10-07 15:37:42 -04:00
parent 4d603d9d41
commit 4f3dbcac6e
No known key found for this signature in database
3 changed files with 92 additions and 10 deletions

View file

@ -9,6 +9,7 @@ import org.jellyfin.sdk.api.client.extensions.imageApi
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ImageType import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.extensions.ticks
@Serializable @Serializable
data class BaseItem( data class BaseItem(
@ -25,6 +26,13 @@ data class BaseItem(
@Transient @Transient
val indexNumber = data.indexNumber val indexNumber = data.indexNumber
@Transient
val resumeMs =
data.userData
?.playbackPositionTicks
?.ticks
?.inWholeMilliseconds
fun destination(): Destination { fun destination(): Destination {
val result = val result =
// Redirect episodes & seasons to their series if possible // Redirect episodes & seasons to their series if possible

View file

@ -11,6 +11,9 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.relocation.BringIntoViewRequester import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.bringIntoViewRequester import androidx.compose.foundation.relocation.bringIntoViewRequester
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -41,6 +44,9 @@ import com.github.damontecres.dolphin.data.model.Video
import com.github.damontecres.dolphin.preferences.UserPreferences import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.cards.ChapterRow import com.github.damontecres.dolphin.ui.cards.ChapterRow
import com.github.damontecres.dolphin.ui.cards.PersonRow import com.github.damontecres.dolphin.ui.cards.PersonRow
import com.github.damontecres.dolphin.ui.components.DialogItem
import com.github.damontecres.dolphin.ui.components.DialogParams
import com.github.damontecres.dolphin.ui.components.DialogPopup
import com.github.damontecres.dolphin.ui.components.ErrorMessage import com.github.damontecres.dolphin.ui.components.ErrorMessage
import com.github.damontecres.dolphin.ui.components.ExpandablePlayButtons import com.github.damontecres.dolphin.ui.components.ExpandablePlayButtons
import com.github.damontecres.dolphin.ui.components.LoadingPage import com.github.damontecres.dolphin.ui.components.LoadingPage
@ -55,9 +61,11 @@ import com.github.damontecres.dolphin.ui.tryRequestFocus
import com.github.damontecres.dolphin.util.ExceptionHandler import com.github.damontecres.dolphin.util.ExceptionHandler
import com.github.damontecres.dolphin.util.LoadingState import com.github.damontecres.dolphin.util.LoadingState
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.playStateApi
import org.jellyfin.sdk.model.extensions.ticks import org.jellyfin.sdk.model.extensions.ticks
import java.util.UUID import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
@ -69,14 +77,16 @@ class MovieViewModel
constructor( constructor(
api: ApiClient, api: ApiClient,
) : LoadingItemViewModel<Video>(api) { ) : LoadingItemViewModel<Video>(api) {
private lateinit var itemId: UUID
val people = MutableLiveData<List<Person>>(listOf()) val people = MutableLiveData<List<Person>>(listOf())
val chapters = MutableLiveData<List<Chapter>>(listOf()) val chapters = MutableLiveData<List<Chapter>>(listOf())
override fun init( override fun init(
itemId: UUID, itemId: UUID,
potential: BaseItem?, potential: BaseItem?,
): Job? = ): Job? {
viewModelScope.launch(ExceptionHandler()) { this.itemId = itemId
return viewModelScope.launch(ExceptionHandler()) {
super.init(itemId, potential)?.join() super.init(itemId, potential)?.join()
item.value?.let { item -> item.value?.let { item ->
people.value = people.value =
@ -86,6 +96,17 @@ class MovieViewModel
chapters.value = Chapter.fromDto(item.data, api) chapters.value = Chapter.fromDto(item.data, api)
} }
} }
}
fun setWatched(played: Boolean) =
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
if (played) {
api.playStateApi.markPlayedItem(itemId)
} else {
api.playStateApi.markUnplayedItem(itemId)
}
init(itemId, null)
}
} }
@Composable @Composable
@ -105,6 +126,7 @@ fun MovieDetails(
val loading by viewModel.loading.observeAsState(LoadingState.Loading) val loading by viewModel.loading.observeAsState(LoadingState.Loading)
var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) } var overviewDialog by remember { mutableStateOf<ItemDetailsDialogInfo?>(null) }
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
when (val state = loading) { when (val state = loading) {
is LoadingState.Error -> ErrorMessage(state) is LoadingState.Error -> ErrorMessage(state)
@ -137,8 +159,46 @@ fun MovieDetails(
.orEmpty(), .orEmpty(),
) )
}, },
moreOnClick = {}, moreOnClick = {
watchOnClick = {}, moreDialog =
DialogParams(
fromLongClick = false,
title = movie.name + " (${movie.data.productionYear ?: ""})",
items =
listOf(
DialogItem(
"Play",
Icons.Default.PlayArrow,
iconColor = Color.Green.copy(alpha = .8f),
) {
navigationManager.navigateTo(
Destination.Playback(
movie.id,
movie.resumeMs ?: 0L,
movie,
),
)
},
DialogItem(
"Playback Settings",
Icons.Default.Settings,
// iconColor = Color.Green.copy(alpha = .8f),
) {
// TODO choose audio or subtitle tracks?
},
DialogItem(
"Play Version",
Icons.Default.PlayArrow,
iconColor = Color.Green.copy(alpha = .8f),
) {
// TODO only show for multiple files
},
),
)
},
watchOnClick = {
viewModel.setWatched((movie.data.userData?.played ?: false).not())
},
modifier = modifier, modifier = modifier,
) )
} }
@ -151,6 +211,16 @@ fun MovieDetails(
modifier = Modifier, modifier = Modifier,
) )
} }
moreDialog?.let { params ->
DialogPopup(
showDialog = true,
title = params.title,
dialogItems = params.items,
onDismissRequest = { moreDialog = null },
dismissOnClick = true,
waitToLoad = params.fromLongClick,
)
}
} }
@Composable @Composable

View file

@ -3,6 +3,7 @@ package com.github.damontecres.dolphin.ui.detail.series
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowForward import androidx.compose.material.icons.automirrored.filled.ArrowForward
import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -192,11 +193,7 @@ fun SeriesOverview(
navigationManager.navigateTo( navigationManager.navigateTo(
Destination.Playback( Destination.Playback(
ep.id, ep.id,
ep.data.userData ep.resumeMs ?: 0L,
?.playbackPositionTicks
?.ticks
?.inWholeMilliseconds
?: 0L,
ep, ep,
), ),
) )
@ -215,12 +212,19 @@ fun SeriesOverview(
), ),
) )
}, },
DialogItem(
"Playback Settings",
Icons.Default.Settings,
// iconColor = Color.Green.copy(alpha = .8f),
) {
// TODO choose audio or subtitle tracks?
},
DialogItem( DialogItem(
"Play Version", "Play Version",
Icons.Default.PlayArrow, Icons.Default.PlayArrow,
iconColor = Color.Green.copy(alpha = .8f), iconColor = Color.Green.copy(alpha = .8f),
) { ) {
// TODO // TODO only show for multiple files
}, },
), ),
) )