diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ccc0f74e..161352af 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + xmlns:tools="http://schemas.android.com/tools" + android:installLocation="auto"> diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt index 9a8643fc..cc561411 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt @@ -752,7 +752,7 @@ sealed interface AppPreference { val PlayerBackendPref = AppChoicePreference( title = R.string.player_backend, - defaultValue = PlayerBackend.PREFER_MPV, + defaultValue = PlayerBackend.EXO_PLAYER, getter = { it.playbackPreferences.playerBackend }, setter = { prefs, value -> prefs.updatePlaybackPreferences { playerBackend = value } diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/AppUpgradeHandler.kt b/app/src/main/java/com/github/damontecres/wholphin/services/AppUpgradeHandler.kt index 09d6a11e..d6fbea40 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/AppUpgradeHandler.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/AppUpgradeHandler.kt @@ -2,26 +2,21 @@ package com.github.damontecres.wholphin.services import android.content.Context import android.os.Build -import android.widget.Toast import androidx.core.content.edit import androidx.datastore.core.DataStore import androidx.preference.PreferenceManager -import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.WholphinApplication import com.github.damontecres.wholphin.preferences.AppPreference import com.github.damontecres.wholphin.preferences.AppPreferences -import com.github.damontecres.wholphin.preferences.PlayerBackend import com.github.damontecres.wholphin.preferences.update import com.github.damontecres.wholphin.preferences.updateAdvancedPreferences import com.github.damontecres.wholphin.preferences.updateInterfacePreferences import com.github.damontecres.wholphin.preferences.updateLiveTvPreferences import com.github.damontecres.wholphin.preferences.updateMpvOptions import com.github.damontecres.wholphin.preferences.updatePlaybackOverrides -import com.github.damontecres.wholphin.preferences.updatePlaybackPreferences import com.github.damontecres.wholphin.preferences.updateSubtitlePreferences import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings -import com.github.damontecres.wholphin.ui.showToast import com.github.damontecres.wholphin.util.Version import dagger.hilt.android.qualifiers.ApplicationContext import timber.log.Timber @@ -203,12 +198,13 @@ suspend fun upgradeApp( } } - if (previous.isEqualOrBefore(Version.fromString("0.4.0-1-g0"))) { - appPreferences.updateData { - it.updatePlaybackPreferences { playerBackend = PlayerBackend.PREFER_MPV } - } - showToast(context, context.getString(R.string.upgrade_mpv_toast), Toast.LENGTH_LONG) - } + // TODO temporarily disabled until some MPV bugs are fixed +// if (previous.isEqualOrBefore(Version.fromString("0.4.0-1-g0"))) { +// appPreferences.updateData { +// it.updatePlaybackPreferences { playerBackend = PlayerBackend.PREFER_MPV } +// } +// showToast(context, context.getString(R.string.upgrade_mpv_toast), Toast.LENGTH_LONG) +// } if (previous.isEqualOrBefore(Version.fromString("0.4.0-2-g0"))) { appPreferences.updateData { diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/StreamChoiceService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/StreamChoiceService.kt index e38957cf..6de45f50 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/StreamChoiceService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/StreamChoiceService.kt @@ -276,10 +276,16 @@ class StreamChoiceService SubtitlePlaybackMode.SMART -> { if (subtitleLanguage.isNotNullOrBlank()) { val audioLanguage = userConfig?.audioLanguagePreference - if (audioLanguage.isNullOrBlank() || audioLanguage != audioStreamLang) { + if ( + // Has preferred subtitle lang & preferred audio, so only show subtitles if actual audio is different + (audioLanguage.isNotNullOrBlank() && audioLanguage != audioStreamLang) || + // Has preferred subtitle lang, but no preferred audio lang, so show subtitle if subtitle lang is different from actual audio + (audioLanguage.isNullOrBlank() && subtitleLanguage != audioStreamLang) + ) { candidates.firstOrNull { it.language == subtitleLanguage } ?: candidates.firstOrNull { it.language.isUnknown } } else { + // Otherwise, show forced subtitles in preferred lang candidates.firstOrNull { it.isForced && it.language == subtitleLanguage } ?: candidates.firstOrNull { it.isForced && it.language.isUnknown } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt index 4dbb0870..12854cd7 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/Extensions.kt @@ -56,7 +56,6 @@ import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext import kotlin.math.min import kotlin.time.Duration -import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.seconds @@ -234,7 +233,7 @@ val Duration.roundMinutes: Duration * Rounds a [Duration] to nearest whole second */ val Duration.roundSeconds: Duration - get() = (this + 30.milliseconds).inWholeSeconds.seconds + get() = (this + .5.seconds).inWholeSeconds.seconds /** * Gets the user's playback position as a [Duration] diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/Dialogs.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/Dialogs.kt index 85ea35cb..d5897a6b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/Dialogs.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/Dialogs.kt @@ -32,6 +32,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.vector.ImageVector @@ -183,7 +184,7 @@ fun DialogPopup( dismissOnClick: Boolean = true, waitToLoad: Boolean = true, properties: DialogProperties = DialogProperties(), - elevation: Dp = 3.dp, + elevation: Dp = 8.dp, ) { var waiting by remember { mutableStateOf(waitToLoad) } if (showDialog) { @@ -238,7 +239,7 @@ fun DialogPopupContent( onDismissRequest: () -> Unit, modifier: Modifier = Modifier, dismissOnClick: Boolean = true, - elevation: Dp = 3.dp, + elevation: Dp = 8.dp, ) { val elevatedContainerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(elevation) @@ -246,6 +247,7 @@ fun DialogPopupContent( verticalArrangement = Arrangement.spacedBy(8.dp), modifier = modifier + .shadow(elevation = elevation, shape = RoundedCornerShape(28.0.dp)) .graphicsLayer { this.clip = true this.shape = RoundedCornerShape(28.0.dp) @@ -296,7 +298,7 @@ fun DialogPopup( onDismissRequest: () -> Unit, dismissOnClick: Boolean = true, properties: DialogProperties = DialogProperties(), - elevation: Dp = 3.dp, + elevation: Dp = 8.dp, ) = DialogPopup( showDialog = true, waitToLoad = params.fromLongClick, @@ -345,6 +347,7 @@ fun ScrollableDialog( .width(width) .heightIn(max = maxHeight) .focusable() + .shadow(elevation = 8.dp, shape = RoundedCornerShape(8.dp)) .background( MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp), shape = RoundedCornerShape(8.dp), @@ -373,7 +376,7 @@ fun ScrollableDialog( fun BasicDialog( onDismissRequest: () -> Unit, properties: DialogProperties = DialogProperties(), - elevation: Dp = 3.dp, + elevation: Dp = 8.dp, content: @Composable () -> Unit, ) { Dialog( @@ -383,6 +386,7 @@ fun BasicDialog( Box( modifier = Modifier + .shadow(elevation = elevation, shape = RoundedCornerShape(8.dp)) .background( MaterialTheme.colorScheme.surfaceColorAtElevation(elevation), shape = RoundedCornerShape(8.dp), @@ -403,7 +407,7 @@ fun ConfirmDialog( onCancel: () -> Unit, onConfirm: () -> Unit, properties: DialogProperties = DialogProperties(), - elevation: Dp = 3.dp, + elevation: Dp = 8.dp, ) = BasicDialog( onDismissRequest = onCancel, properties = properties, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetailsHeader.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetailsHeader.kt index 4da6f938..79ccb8dd 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetailsHeader.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetailsHeader.kt @@ -25,6 +25,7 @@ import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.ui.components.GenreText import com.github.damontecres.wholphin.ui.components.OverviewText import com.github.damontecres.wholphin.ui.components.QuickDetails +import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.letNotEmpty import com.github.damontecres.wholphin.ui.listToDotString import com.github.damontecres.wholphin.ui.roundMinutes @@ -103,7 +104,8 @@ fun DiscoverMovieDetailsHeader( GenreText(it, Modifier.padding(bottom = padding)) } - movie.tagline?.let { tagline -> + val tagline = remember { movie.tagline?.takeIf { it.isNotNullOrBlank() } } + tagline?.let { Text( text = tagline, style = MaterialTheme.typography.bodyLarge, @@ -134,8 +136,9 @@ fun DiscoverMovieDetailsHeader( remember(movie.credits?.crew) { movie.credits ?.crew - ?.filter { it.job == "Directing" } + ?.filter { it.job == "Director" && it.name.isNotNullOrBlank() } ?.joinToString(", ") { it.name!! } + ?.takeIf { it.isNotNullOrBlank() } } directorName diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt index 63010fde..e3edbb6b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt @@ -284,6 +284,7 @@ fun DestinationContent( } SeerrItemType.PERSON -> { + LaunchedEffect(Unit) { onClearBackdrop.invoke() } DiscoverPersonPage( person = destination.item, modifier = modifier, diff --git a/app/src/test/java/com/github/damontecres/wholphin/test/TestStreamChoiceService.kt b/app/src/test/java/com/github/damontecres/wholphin/test/TestStreamChoiceService.kt index e41bf292..f4d04c7e 100644 --- a/app/src/test/java/com/github/damontecres/wholphin/test/TestStreamChoiceService.kt +++ b/app/src/test/java/com/github/damontecres/wholphin/test/TestStreamChoiceService.kt @@ -355,7 +355,7 @@ class TestStreamChoiceServiceSmart( userAudioLang = null, ), TestInput( - 1, + null, SubtitlePlaybackMode.SMART, subtitles = listOf( @@ -367,6 +367,18 @@ class TestStreamChoiceServiceSmart( userSubtitleLang = "eng", userAudioLang = null, ), + TestInput( + null, + SubtitlePlaybackMode.SMART, + subtitles = + listOf( + subtitle(0, "eng"), + subtitle(1, "spa"), + ), + streamAudioLang = "spa", + userSubtitleLang = "spa", + userAudioLang = null, + ), ) } } @@ -498,7 +510,7 @@ class TestStreamChoiceServiceMultipleChoices( ), ), TestInput( - 2, + 0, SubtitlePlaybackMode.SMART, subtitles = listOf( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ae490672..10832e4a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,10 +13,10 @@ kotlinxCoroutinesCore = "1.10.2" ksp = "2.3.0" coreKtx = "1.17.0" appcompat = "1.7.1" -composeBom = "2025.12.01" +composeBom = "2026.01.01" mockk = "1.14.7" -robolectric = "4.14.1" -multiplatformMarkdownRenderer = "0.39.0" +robolectric = "4.16.1" +multiplatformMarkdownRenderer = "0.39.1" okhttpBom = "5.3.2" programguide = "1.6.0" slf4j2Timber = "1.2" @@ -24,8 +24,8 @@ timber = "5.0.1" tvFoundation = "1.0.0-alpha12" tvMaterial = "1.0.1" lifecycleRuntimeKtx = "2.10.0" -activityCompose = "1.12.2" -androidx-media3 = "1.9.0" +activityCompose = "1.12.3" +androidx-media3 = "1.9.1" coil = "3.3.0" jellyfin-sdk = "1.7.1" nav3Core = "1.0.0" @@ -33,15 +33,15 @@ lifecycleViewmodelNav3 = "2.10.0" material3AdaptiveNav3 = "1.0.0-alpha03" protobuf = "0.9.6" datastore = "1.2.0" -kotlinx-serialization = "1.9.0" -protobuf-javalite = "4.33.2" -hilt = "2.57.2" +kotlinx-serialization = "1.10.0" +protobuf-javalite = "4.33.4" +hilt = "2.58" room = "2.8.4" preferenceKtx = "1.2.1" tvprovider = "1.1.0" -workRuntimeKtx = "2.11.0" +workRuntimeKtx = "2.11.1" paletteKtx = "1.0.0" -openapi-generator = "7.18.0" +openapi-generator = "7.19.0" [libraries] aboutlibraries-core = { module = "com.mikepenz:aboutlibraries-core", version.ref = "aboutLibraries" }