From 5975302fb97c4f8dac7bbd66df2f54ab01b711b7 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Wed, 28 Jan 2026 13:56:20 -0500 Subject: [PATCH 1/6] Allow app to install on external storage #733 --- app/src/main/AndroidManifest.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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"> From f787e9ce1a8d1f2269ce84d0e212e3c7b593eaed Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Wed, 28 Jan 2026 13:56:33 -0500 Subject: [PATCH 2/6] Better smart subtitles with no preferred audio language (#789) ## Description Another small fix to smart subtitle track selection to better handle the cases where the user does not have a preferred audio language. ### Related issues Addresses issue raises in https://github.com/damontecres/Wholphin/issues/570#issuecomment-3714629517 --- .../wholphin/services/StreamChoiceService.kt | 8 +++++++- .../wholphin/test/TestStreamChoiceService.kt | 16 ++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) 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/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( From 792d3598b784766b03569f15b100d8b89d0511b5 Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Thu, 29 Jan 2026 13:31:51 -0500 Subject: [PATCH 3/6] Fix a few discover UI bugs (#795) ## Description Just fixes a couple small UI issues on the discover/seerr pages --- .../java/com/github/damontecres/wholphin/ui/Extensions.kt | 3 +-- .../ui/detail/discover/DiscoverMovieDetailsHeader.kt | 7 +++++-- .../damontecres/wholphin/ui/nav/DestinationContent.kt | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) 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/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 43b8841d..70ef71d2 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 @@ -279,6 +279,7 @@ fun DestinationContent( } SeerrItemType.PERSON -> { + LaunchedEffect(Unit) { onClearBackdrop.invoke() } DiscoverPersonPage( person = destination.item, modifier = modifier, From b3ef60f86d7cdebda54b6847ba6d0e9afc8fa380 Mon Sep 17 00:00:00 2001 From: Justin Caveda Date: Thu, 29 Jan 2026 12:32:00 -0600 Subject: [PATCH 4/6] Feat(ui): Add drop shadow to UI dialogs (#678) ## Description Adds a subtle drop shadow to dialog containers in Dialogs.kt. Should make the dialogs pop a bit more instead of looking flat. ### Related issues Fixes #665 ### Screenshots Before: before1 After: after ### AI/LLM usage None --------- Co-authored-by: Ray <154766448+damontecres@users.noreply.github.com> --- .../damontecres/wholphin/ui/components/Dialogs.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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, From fa51d1e61e199414b48bd6ca832882ae6c4bb7da Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Thu, 29 Jan 2026 14:26:54 -0500 Subject: [PATCH 5/6] Bump dependency versions (#796) ## Description Bumps dependency versions. AGP (and hilt by extension) are not yet updated cause moving to AGP 9 has many breaking changes and I think one or two may affect the build scripts. Also, one day I will add Renovate to automate these PRs. --- gradle/libs.versions.toml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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" } From abd8235556f10431e5755a5b61315784103b8ef4 Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Thu, 29 Jan 2026 14:27:01 -0500 Subject: [PATCH 6/6] Temporarily restore ExoPlayer as the default player (#797) ## Description Due a few more MPV bugs showing up, this PR reverts the part of #736 that made "Prefer MPV" the default player option. User can still manually switch to "Prefer MPV" and its full functional remains. It's just not set to be the default anymore. --- .../wholphin/preferences/AppPreference.kt | 2 +- .../wholphin/services/AppUpgradeHandler.kt | 18 +++++++----------- 2 files changed, 8 insertions(+), 12 deletions(-) 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 99b457a5..70e83692 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 @@ -746,7 +746,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 {