mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Merge branch 'main' into fea/music
This commit is contained in:
commit
9badecf086
14 changed files with 115 additions and 31 deletions
|
|
@ -135,7 +135,6 @@ fun AppScreensaverContent(
|
|||
)
|
||||
|
||||
var logoError by remember(currentItem) { mutableStateOf(false) }
|
||||
val alignment = listOf(Alignment.BottomStart, Alignment.BottomEnd).random()
|
||||
if (!logoError) {
|
||||
AsyncImage(
|
||||
model =
|
||||
|
|
@ -150,7 +149,7 @@ fun AppScreensaverContent(
|
|||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.align(alignment)
|
||||
.align(Alignment.BottomStart)
|
||||
.size(width = 240.dp, height = 120.dp)
|
||||
.padding(16.dp),
|
||||
)
|
||||
|
|
@ -158,7 +157,7 @@ fun AppScreensaverContent(
|
|||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.align(alignment)
|
||||
.align(Alignment.BottomStart)
|
||||
.padding(16.dp)
|
||||
.fillMaxWidth(.5f)
|
||||
.fillMaxHeight(.3f),
|
||||
|
|
@ -169,7 +168,7 @@ fun AppScreensaverContent(
|
|||
style = MaterialTheme.typography.displaySmall,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.align(alignment),
|
||||
modifier = Modifier.align(Alignment.BottomStart),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -632,6 +632,7 @@ fun chooseStream(
|
|||
streams: List<MediaStream>,
|
||||
currentIndex: Int?,
|
||||
type: MediaStreamType,
|
||||
preferredSubtitleLanguage: String?,
|
||||
onClick: (Int) -> Unit,
|
||||
): DialogParams =
|
||||
DialogParams(
|
||||
|
|
@ -669,7 +670,15 @@ fun chooseStream(
|
|||
)
|
||||
}
|
||||
addAll(
|
||||
streams.filter { it.type == type }.mapIndexed { index, stream ->
|
||||
streams
|
||||
.filter { it.type == type }
|
||||
.let {
|
||||
if (type == MediaStreamType.SUBTITLE && preferredSubtitleLanguage.isNotNullOrBlank()) {
|
||||
it.sortedByDescending { it.language != null && it.language == preferredSubtitleLanguage }
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}.mapIndexed { index, stream ->
|
||||
val simpleStream = SimpleMediaStream.from(context, stream, true)
|
||||
DialogItem(
|
||||
selected = currentIndex == stream.index,
|
||||
|
|
@ -677,7 +686,9 @@ fun chooseStream(
|
|||
SelectedLeadingContent(currentIndex == stream.index)
|
||||
},
|
||||
headlineContent = {
|
||||
Text(text = simpleStream.streamTitle ?: simpleStream.displayTitle)
|
||||
Text(
|
||||
text = simpleStream.streamTitle ?: simpleStream.displayTitle,
|
||||
)
|
||||
},
|
||||
supportingContent = {
|
||||
if (simpleStream.streamTitle != null) Text(text = simpleStream.displayTitle)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.widget.Toast
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.services.PlaylistCreator
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
|
|
@ -14,6 +15,7 @@ import com.github.damontecres.wholphin.util.ExceptionHandler
|
|||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -43,7 +45,13 @@ class AddPlaylistViewModel
|
|||
itemId: UUID,
|
||||
) {
|
||||
viewModelScope.launchIO(ExceptionHandler(autoToast = true)) {
|
||||
try {
|
||||
playlistCreator.addToServerPlaylist(playlistId, itemId)
|
||||
showToast(context, context.getString(R.string.success), Toast.LENGTH_SHORT)
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Error adding %s to playlist %s", itemId, playlistId)
|
||||
showToast(context, "Error: ${ex.localizedMessage}", Toast.LENGTH_SHORT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -55,6 +63,8 @@ class AddPlaylistViewModel
|
|||
val playlistId = playlistCreator.createServerPlaylist(playlistName, listOf(itemId))
|
||||
if (playlistId == null) {
|
||||
showToast(context, "Error creating playlist", Toast.LENGTH_LONG)
|
||||
} else {
|
||||
showToast(context, context.getString(R.string.success), Toast.LENGTH_SHORT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,13 @@ fun EpisodeDetails(
|
|||
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
|
||||
val preferredSubtitleLanguage =
|
||||
viewModel.serverRepository.currentUserDto
|
||||
.observeAsState()
|
||||
.value
|
||||
?.configuration
|
||||
?.subtitleLanguagePreference
|
||||
|
||||
val moreActions =
|
||||
MoreDialogActions(
|
||||
navigateTo = viewModel::navigateTo,
|
||||
|
|
@ -200,6 +207,7 @@ fun EpisodeDetails(
|
|||
type,
|
||||
)
|
||||
},
|
||||
preferredSubtitleLanguage = preferredSubtitleLanguage,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -114,6 +114,13 @@ fun MovieDetails(
|
|||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
var showDeleteDialog by remember { mutableStateOf<BaseItem?>(null) }
|
||||
|
||||
val preferredSubtitleLanguage =
|
||||
viewModel.serverRepository.currentUserDto
|
||||
.observeAsState()
|
||||
.value
|
||||
?.configuration
|
||||
?.subtitleLanguagePreference
|
||||
|
||||
val moreActions =
|
||||
MoreDialogActions(
|
||||
navigateTo = viewModel::navigateTo,
|
||||
|
|
@ -246,6 +253,7 @@ fun MovieDetails(
|
|||
type,
|
||||
)
|
||||
},
|
||||
preferredSubtitleLanguage = preferredSubtitleLanguage,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -138,6 +138,13 @@ fun SeriesOverview(
|
|||
}
|
||||
val chosenStreams by viewModel.chosenStreams.observeAsState(null)
|
||||
|
||||
val preferredSubtitleLanguage =
|
||||
viewModel.serverRepository.currentUserDto
|
||||
.observeAsState()
|
||||
.value
|
||||
?.configuration
|
||||
?.subtitleLanguagePreference
|
||||
|
||||
when (val state = loading) {
|
||||
is LoadingState.Error -> {
|
||||
ErrorMessage(state, modifier)
|
||||
|
|
@ -252,6 +259,7 @@ fun SeriesOverview(
|
|||
type,
|
||||
)
|
||||
},
|
||||
preferredSubtitleLanguage = preferredSubtitleLanguage,
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
|||
import com.github.damontecres.wholphin.ui.components.Button
|
||||
import com.github.damontecres.wholphin.ui.components.SelectedLeadingContent
|
||||
import com.github.damontecres.wholphin.ui.components.TextButton
|
||||
import com.github.damontecres.wholphin.ui.indexOfFirstOrNull
|
||||
import com.github.damontecres.wholphin.ui.seekBack
|
||||
import com.github.damontecres.wholphin.ui.seekForward
|
||||
import com.github.damontecres.wholphin.ui.skipStringRes
|
||||
|
|
@ -522,6 +523,14 @@ fun <T> BottomDialog(
|
|||
gravity: Int,
|
||||
currentChoice: BottomDialogItem<T>? = null,
|
||||
) {
|
||||
val focusRequesters = remember(choices.size) { List(choices.size) { FocusRequester() } }
|
||||
if (currentChoice != null) {
|
||||
LaunchedEffect(Unit) {
|
||||
choices.indexOfFirstOrNull { it == currentChoice }?.let {
|
||||
focusRequesters.getOrNull(it)?.tryRequestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO enforcing a width ends up ignore the gravity
|
||||
Dialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
|
|
@ -556,6 +565,7 @@ fun <T> BottomDialog(
|
|||
val interactionSource = remember { MutableInteractionSource() }
|
||||
ListItem(
|
||||
selected = choice == currentChoice,
|
||||
enabled = choice.enabled,
|
||||
onClick = {
|
||||
onDismissRequest()
|
||||
onSelectChoice(index, choice)
|
||||
|
|
@ -576,6 +586,7 @@ fun <T> BottomDialog(
|
|||
}
|
||||
},
|
||||
interactionSource = interactionSource,
|
||||
modifier = Modifier.focusRequester(focusRequesters[index]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -591,6 +602,7 @@ data class BottomDialogItem<T>(
|
|||
val data: T,
|
||||
val headline: String,
|
||||
val supporting: String?,
|
||||
val enabled: Boolean = true,
|
||||
)
|
||||
|
||||
@PreviewTvSpec
|
||||
|
|
|
|||
|
|
@ -14,9 +14,12 @@ import androidx.compose.foundation.lazy.itemsIndexed
|
|||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
|
@ -32,6 +35,8 @@ import com.github.damontecres.wholphin.R
|
|||
import com.github.damontecres.wholphin.data.model.TrackIndex
|
||||
import com.github.damontecres.wholphin.ui.AppColors
|
||||
import com.github.damontecres.wholphin.ui.components.SelectedLeadingContent
|
||||
import com.github.damontecres.wholphin.ui.indexOfFirstOrNull
|
||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||
import kotlin.time.Duration
|
||||
|
||||
enum class PlaybackDialogType {
|
||||
|
|
@ -54,6 +59,7 @@ data class PlaybackSettings(
|
|||
val contentScale: ContentScale,
|
||||
val subtitleDelay: Duration,
|
||||
val hasSubtitleDownloadPermission: Boolean,
|
||||
val playbackSpeedEnabled: Boolean,
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
|
@ -137,6 +143,7 @@ fun PlaybackDialog(
|
|||
data = PlaybackDialogType.PLAYBACK_SPEED,
|
||||
headline = stringResource(R.string.playback_speed),
|
||||
supporting = settings.playbackSpeed.toString(),
|
||||
enabled = settings.playbackSpeedEnabled,
|
||||
),
|
||||
)
|
||||
if (enableVideoScale) {
|
||||
|
|
@ -395,6 +402,14 @@ fun StreamChoiceBottomDialog(
|
|||
gravity: Int,
|
||||
currentChoice: Int? = null,
|
||||
) {
|
||||
val focusRequesters = remember(choices.size) { List(choices.size) { FocusRequester() } }
|
||||
if (currentChoice != null) {
|
||||
LaunchedEffect(Unit) {
|
||||
choices.indexOfFirstOrNull { it.index == currentChoice }?.let {
|
||||
focusRequesters.getOrNull(it)?.tryRequestFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO enforcing a width ends up ignore the gravity
|
||||
Dialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
|
|
@ -445,6 +460,7 @@ fun StreamChoiceBottomDialog(
|
|||
if (choice.streamTitle != null) Text(choice.displayTitle)
|
||||
},
|
||||
interactionSource = interactionSource,
|
||||
modifier = Modifier.focusRequester(focusRequesters[index]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -596,6 +596,9 @@ fun PlaybackPageContent(
|
|||
subtitleDelay = subtitleDelay,
|
||||
hasSubtitleDownloadPermission =
|
||||
remember(userDto) { userDto?.policy?.let { it.isAdministrator || it.enableSubtitleManagement } == true },
|
||||
// TODO Passing through audio prevents changing playback speed
|
||||
// See https://github.com/damontecres/Wholphin/issues/164
|
||||
playbackSpeedEnabled = playerBackend == PlayerBackend.MPV || currentPlayback?.audioDecoder != null,
|
||||
),
|
||||
onDismissRequest = {
|
||||
playbackDialog = null
|
||||
|
|
|
|||
|
|
@ -449,11 +449,20 @@ class PlaybackViewModel
|
|||
|
||||
// Create the correct player for the media
|
||||
createPlayer(videoStream?.hdr == true, videoStream?.is4k == true)
|
||||
|
||||
val subtitleLanguagePreference =
|
||||
serverRepository.currentUserDto.value
|
||||
?.configuration
|
||||
?.subtitleLanguagePreference
|
||||
val subtitleStreams =
|
||||
mediaSource.mediaStreams
|
||||
?.filter { it.type == MediaStreamType.SUBTITLE }
|
||||
?.map {
|
||||
.let {
|
||||
if (subtitleLanguagePreference.isNotNullOrBlank()) {
|
||||
it?.sortedByDescending { it.language != null && subtitleLanguagePreference == it.language }
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}?.map {
|
||||
SimpleMediaStream.from(context, it, true)
|
||||
}.orEmpty()
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ desugar_jdk_libs = "2.1.5"
|
|||
hiltNavigationCompose = "1.3.0"
|
||||
hiltWork = "1.3.0"
|
||||
kache = "2.1.1"
|
||||
kotlin = "2.3.10"
|
||||
kotlin = "2.3.20"
|
||||
ksp = "2.3.6"
|
||||
coreKtx = "1.17.0"
|
||||
coreKtx = "1.18.0"
|
||||
appcompat = "1.7.1"
|
||||
composeBom = "2026.02.01"
|
||||
composeBom = "2026.03.00"
|
||||
mockk = "1.14.9"
|
||||
robolectric = "4.16.1"
|
||||
multiplatformMarkdownRenderer = "0.39.2"
|
||||
|
|
@ -20,10 +20,10 @@ okhttpBom = "5.3.2"
|
|||
programguide = "1.6.0"
|
||||
slf4j2Timber = "1.2"
|
||||
timber = "5.0.1"
|
||||
tvFoundation = "1.0.0-alpha12"
|
||||
tvFoundation = "1.0.0-beta01"
|
||||
tvMaterial = "1.0.1"
|
||||
lifecycleRuntimeKtx = "2.10.0"
|
||||
activityCompose = "1.12.4"
|
||||
activityCompose = "1.13.0"
|
||||
androidx-media3 = "1.9.2"
|
||||
coil = "3.4.0"
|
||||
jellyfin-sdk = "1.7.1"
|
||||
|
|
@ -31,7 +31,7 @@ nav3Core = "1.0.1"
|
|||
lifecycleViewmodelNav3 = "2.10.0"
|
||||
material3AdaptiveNav3 = "1.0.0-alpha03"
|
||||
protobuf = "0.9.6"
|
||||
datastore = "1.2.0"
|
||||
datastore = "1.2.1"
|
||||
kotlinx-serialization = "1.10.0"
|
||||
protobuf-javalite = "4.34.0"
|
||||
hilt = "2.59.2"
|
||||
|
|
|
|||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,6 +1,6 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
|
|||
2
gradlew
vendored
2
gradlew
vendored
|
|
@ -57,7 +57,7 @@
|
|||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue