Merge branch 'main' into fea/customize-home

This commit is contained in:
Damontecres 2026-01-29 14:27:19 -05:00
commit 01bdf2cefa
No known key found for this signature in database
10 changed files with 57 additions and 35 deletions

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools"
android:installLocation="auto">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />

View file

@ -752,7 +752,7 @@ sealed interface AppPreference<Pref, T> {
val PlayerBackendPref =
AppChoicePreference<AppPreferences, PlayerBackend>(
title = R.string.player_backend,
defaultValue = PlayerBackend.PREFER_MPV,
defaultValue = PlayerBackend.EXO_PLAYER,
getter = { it.playbackPreferences.playerBackend },
setter = { prefs, value ->
prefs.updatePlaybackPreferences { playerBackend = value }

View file

@ -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 {

View file

@ -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 }
}

View file

@ -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]

View file

@ -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,

View file

@ -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

View file

@ -284,6 +284,7 @@ fun DestinationContent(
}
SeerrItemType.PERSON -> {
LaunchedEffect(Unit) { onClearBackdrop.invoke() }
DiscoverPersonPage(
person = destination.item,
modifier = modifier,

View file

@ -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(

View file

@ -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" }