mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Merge branch 'main' into fea/customize-home
This commit is contained in:
commit
01bdf2cefa
10 changed files with 57 additions and 35 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<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.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
|
|
|
||||||
|
|
@ -752,7 +752,7 @@ sealed interface AppPreference<Pref, T> {
|
||||||
val PlayerBackendPref =
|
val PlayerBackendPref =
|
||||||
AppChoicePreference<AppPreferences, PlayerBackend>(
|
AppChoicePreference<AppPreferences, PlayerBackend>(
|
||||||
title = R.string.player_backend,
|
title = R.string.player_backend,
|
||||||
defaultValue = PlayerBackend.PREFER_MPV,
|
defaultValue = PlayerBackend.EXO_PLAYER,
|
||||||
getter = { it.playbackPreferences.playerBackend },
|
getter = { it.playbackPreferences.playerBackend },
|
||||||
setter = { prefs, value ->
|
setter = { prefs, value ->
|
||||||
prefs.updatePlaybackPreferences { playerBackend = value }
|
prefs.updatePlaybackPreferences { playerBackend = value }
|
||||||
|
|
|
||||||
|
|
@ -2,26 +2,21 @@ package com.github.damontecres.wholphin.services
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.github.damontecres.wholphin.R
|
|
||||||
import com.github.damontecres.wholphin.WholphinApplication
|
import com.github.damontecres.wholphin.WholphinApplication
|
||||||
import com.github.damontecres.wholphin.preferences.AppPreference
|
import com.github.damontecres.wholphin.preferences.AppPreference
|
||||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
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.update
|
||||||
import com.github.damontecres.wholphin.preferences.updateAdvancedPreferences
|
import com.github.damontecres.wholphin.preferences.updateAdvancedPreferences
|
||||||
import com.github.damontecres.wholphin.preferences.updateInterfacePreferences
|
import com.github.damontecres.wholphin.preferences.updateInterfacePreferences
|
||||||
import com.github.damontecres.wholphin.preferences.updateLiveTvPreferences
|
import com.github.damontecres.wholphin.preferences.updateLiveTvPreferences
|
||||||
import com.github.damontecres.wholphin.preferences.updateMpvOptions
|
import com.github.damontecres.wholphin.preferences.updateMpvOptions
|
||||||
import com.github.damontecres.wholphin.preferences.updatePlaybackOverrides
|
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.preferences.updateSubtitlePreferences
|
||||||
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
|
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
|
||||||
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings
|
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings
|
||||||
import com.github.damontecres.wholphin.ui.showToast
|
|
||||||
import com.github.damontecres.wholphin.util.Version
|
import com.github.damontecres.wholphin.util.Version
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
@ -203,12 +198,13 @@ suspend fun upgradeApp(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (previous.isEqualOrBefore(Version.fromString("0.4.0-1-g0"))) {
|
// TODO temporarily disabled until some MPV bugs are fixed
|
||||||
appPreferences.updateData {
|
// if (previous.isEqualOrBefore(Version.fromString("0.4.0-1-g0"))) {
|
||||||
it.updatePlaybackPreferences { playerBackend = PlayerBackend.PREFER_MPV }
|
// appPreferences.updateData {
|
||||||
}
|
// it.updatePlaybackPreferences { playerBackend = PlayerBackend.PREFER_MPV }
|
||||||
showToast(context, context.getString(R.string.upgrade_mpv_toast), Toast.LENGTH_LONG)
|
// }
|
||||||
}
|
// showToast(context, context.getString(R.string.upgrade_mpv_toast), Toast.LENGTH_LONG)
|
||||||
|
// }
|
||||||
|
|
||||||
if (previous.isEqualOrBefore(Version.fromString("0.4.0-2-g0"))) {
|
if (previous.isEqualOrBefore(Version.fromString("0.4.0-2-g0"))) {
|
||||||
appPreferences.updateData {
|
appPreferences.updateData {
|
||||||
|
|
|
||||||
|
|
@ -276,10 +276,16 @@ class StreamChoiceService
|
||||||
SubtitlePlaybackMode.SMART -> {
|
SubtitlePlaybackMode.SMART -> {
|
||||||
if (subtitleLanguage.isNotNullOrBlank()) {
|
if (subtitleLanguage.isNotNullOrBlank()) {
|
||||||
val audioLanguage = userConfig?.audioLanguagePreference
|
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 == subtitleLanguage }
|
||||||
?: candidates.firstOrNull { it.language.isUnknown }
|
?: candidates.firstOrNull { it.language.isUnknown }
|
||||||
} else {
|
} else {
|
||||||
|
// Otherwise, show forced subtitles in preferred lang
|
||||||
candidates.firstOrNull { it.isForced && it.language == subtitleLanguage }
|
candidates.firstOrNull { it.isForced && it.language == subtitleLanguage }
|
||||||
?: candidates.firstOrNull { it.isForced && it.language.isUnknown }
|
?: candidates.firstOrNull { it.isForced && it.language.isUnknown }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,6 @@ import kotlin.contracts.contract
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
|
||||||
import kotlin.time.Duration.Companion.minutes
|
import kotlin.time.Duration.Companion.minutes
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
|
@ -234,7 +233,7 @@ val Duration.roundMinutes: Duration
|
||||||
* Rounds a [Duration] to nearest whole second
|
* Rounds a [Duration] to nearest whole second
|
||||||
*/
|
*/
|
||||||
val Duration.roundSeconds: Duration
|
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]
|
* Gets the user's playback position as a [Duration]
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.drawBehind
|
import androidx.compose.ui.draw.drawBehind
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
|
@ -183,7 +184,7 @@ fun DialogPopup(
|
||||||
dismissOnClick: Boolean = true,
|
dismissOnClick: Boolean = true,
|
||||||
waitToLoad: Boolean = true,
|
waitToLoad: Boolean = true,
|
||||||
properties: DialogProperties = DialogProperties(),
|
properties: DialogProperties = DialogProperties(),
|
||||||
elevation: Dp = 3.dp,
|
elevation: Dp = 8.dp,
|
||||||
) {
|
) {
|
||||||
var waiting by remember { mutableStateOf(waitToLoad) }
|
var waiting by remember { mutableStateOf(waitToLoad) }
|
||||||
if (showDialog) {
|
if (showDialog) {
|
||||||
|
|
@ -238,7 +239,7 @@ fun DialogPopupContent(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
dismissOnClick: Boolean = true,
|
dismissOnClick: Boolean = true,
|
||||||
elevation: Dp = 3.dp,
|
elevation: Dp = 8.dp,
|
||||||
) {
|
) {
|
||||||
val elevatedContainerColor =
|
val elevatedContainerColor =
|
||||||
MaterialTheme.colorScheme.surfaceColorAtElevation(elevation)
|
MaterialTheme.colorScheme.surfaceColorAtElevation(elevation)
|
||||||
|
|
@ -246,6 +247,7 @@ fun DialogPopupContent(
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
modifier =
|
modifier =
|
||||||
modifier
|
modifier
|
||||||
|
.shadow(elevation = elevation, shape = RoundedCornerShape(28.0.dp))
|
||||||
.graphicsLayer {
|
.graphicsLayer {
|
||||||
this.clip = true
|
this.clip = true
|
||||||
this.shape = RoundedCornerShape(28.0.dp)
|
this.shape = RoundedCornerShape(28.0.dp)
|
||||||
|
|
@ -296,7 +298,7 @@ fun DialogPopup(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
dismissOnClick: Boolean = true,
|
dismissOnClick: Boolean = true,
|
||||||
properties: DialogProperties = DialogProperties(),
|
properties: DialogProperties = DialogProperties(),
|
||||||
elevation: Dp = 3.dp,
|
elevation: Dp = 8.dp,
|
||||||
) = DialogPopup(
|
) = DialogPopup(
|
||||||
showDialog = true,
|
showDialog = true,
|
||||||
waitToLoad = params.fromLongClick,
|
waitToLoad = params.fromLongClick,
|
||||||
|
|
@ -345,6 +347,7 @@ fun ScrollableDialog(
|
||||||
.width(width)
|
.width(width)
|
||||||
.heightIn(max = maxHeight)
|
.heightIn(max = maxHeight)
|
||||||
.focusable()
|
.focusable()
|
||||||
|
.shadow(elevation = 8.dp, shape = RoundedCornerShape(8.dp))
|
||||||
.background(
|
.background(
|
||||||
MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp),
|
MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp),
|
||||||
shape = RoundedCornerShape(8.dp),
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
|
@ -373,7 +376,7 @@ fun ScrollableDialog(
|
||||||
fun BasicDialog(
|
fun BasicDialog(
|
||||||
onDismissRequest: () -> Unit,
|
onDismissRequest: () -> Unit,
|
||||||
properties: DialogProperties = DialogProperties(),
|
properties: DialogProperties = DialogProperties(),
|
||||||
elevation: Dp = 3.dp,
|
elevation: Dp = 8.dp,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
Dialog(
|
Dialog(
|
||||||
|
|
@ -383,6 +386,7 @@ fun BasicDialog(
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
|
.shadow(elevation = elevation, shape = RoundedCornerShape(8.dp))
|
||||||
.background(
|
.background(
|
||||||
MaterialTheme.colorScheme.surfaceColorAtElevation(elevation),
|
MaterialTheme.colorScheme.surfaceColorAtElevation(elevation),
|
||||||
shape = RoundedCornerShape(8.dp),
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
|
@ -403,7 +407,7 @@ fun ConfirmDialog(
|
||||||
onCancel: () -> Unit,
|
onCancel: () -> Unit,
|
||||||
onConfirm: () -> Unit,
|
onConfirm: () -> Unit,
|
||||||
properties: DialogProperties = DialogProperties(),
|
properties: DialogProperties = DialogProperties(),
|
||||||
elevation: Dp = 3.dp,
|
elevation: Dp = 8.dp,
|
||||||
) = BasicDialog(
|
) = BasicDialog(
|
||||||
onDismissRequest = onCancel,
|
onDismissRequest = onCancel,
|
||||||
properties = properties,
|
properties = properties,
|
||||||
|
|
|
||||||
|
|
@ -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.GenreText
|
||||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||||
import com.github.damontecres.wholphin.ui.components.QuickDetails
|
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.letNotEmpty
|
||||||
import com.github.damontecres.wholphin.ui.listToDotString
|
import com.github.damontecres.wholphin.ui.listToDotString
|
||||||
import com.github.damontecres.wholphin.ui.roundMinutes
|
import com.github.damontecres.wholphin.ui.roundMinutes
|
||||||
|
|
@ -103,7 +104,8 @@ fun DiscoverMovieDetailsHeader(
|
||||||
GenreText(it, Modifier.padding(bottom = padding))
|
GenreText(it, Modifier.padding(bottom = padding))
|
||||||
}
|
}
|
||||||
|
|
||||||
movie.tagline?.let { tagline ->
|
val tagline = remember { movie.tagline?.takeIf { it.isNotNullOrBlank() } }
|
||||||
|
tagline?.let {
|
||||||
Text(
|
Text(
|
||||||
text = tagline,
|
text = tagline,
|
||||||
style = MaterialTheme.typography.bodyLarge,
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
|
@ -134,8 +136,9 @@ fun DiscoverMovieDetailsHeader(
|
||||||
remember(movie.credits?.crew) {
|
remember(movie.credits?.crew) {
|
||||||
movie.credits
|
movie.credits
|
||||||
?.crew
|
?.crew
|
||||||
?.filter { it.job == "Directing" }
|
?.filter { it.job == "Director" && it.name.isNotNullOrBlank() }
|
||||||
?.joinToString(", ") { it.name!! }
|
?.joinToString(", ") { it.name!! }
|
||||||
|
?.takeIf { it.isNotNullOrBlank() }
|
||||||
}
|
}
|
||||||
|
|
||||||
directorName
|
directorName
|
||||||
|
|
|
||||||
|
|
@ -284,6 +284,7 @@ fun DestinationContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
SeerrItemType.PERSON -> {
|
SeerrItemType.PERSON -> {
|
||||||
|
LaunchedEffect(Unit) { onClearBackdrop.invoke() }
|
||||||
DiscoverPersonPage(
|
DiscoverPersonPage(
|
||||||
person = destination.item,
|
person = destination.item,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
|
|
||||||
|
|
@ -355,7 +355,7 @@ class TestStreamChoiceServiceSmart(
|
||||||
userAudioLang = null,
|
userAudioLang = null,
|
||||||
),
|
),
|
||||||
TestInput(
|
TestInput(
|
||||||
1,
|
null,
|
||||||
SubtitlePlaybackMode.SMART,
|
SubtitlePlaybackMode.SMART,
|
||||||
subtitles =
|
subtitles =
|
||||||
listOf(
|
listOf(
|
||||||
|
|
@ -367,6 +367,18 @@ class TestStreamChoiceServiceSmart(
|
||||||
userSubtitleLang = "eng",
|
userSubtitleLang = "eng",
|
||||||
userAudioLang = null,
|
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(
|
TestInput(
|
||||||
2,
|
0,
|
||||||
SubtitlePlaybackMode.SMART,
|
SubtitlePlaybackMode.SMART,
|
||||||
subtitles =
|
subtitles =
|
||||||
listOf(
|
listOf(
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,10 @@ kotlinxCoroutinesCore = "1.10.2"
|
||||||
ksp = "2.3.0"
|
ksp = "2.3.0"
|
||||||
coreKtx = "1.17.0"
|
coreKtx = "1.17.0"
|
||||||
appcompat = "1.7.1"
|
appcompat = "1.7.1"
|
||||||
composeBom = "2025.12.01"
|
composeBom = "2026.01.01"
|
||||||
mockk = "1.14.7"
|
mockk = "1.14.7"
|
||||||
robolectric = "4.14.1"
|
robolectric = "4.16.1"
|
||||||
multiplatformMarkdownRenderer = "0.39.0"
|
multiplatformMarkdownRenderer = "0.39.1"
|
||||||
okhttpBom = "5.3.2"
|
okhttpBom = "5.3.2"
|
||||||
programguide = "1.6.0"
|
programguide = "1.6.0"
|
||||||
slf4j2Timber = "1.2"
|
slf4j2Timber = "1.2"
|
||||||
|
|
@ -24,8 +24,8 @@ timber = "5.0.1"
|
||||||
tvFoundation = "1.0.0-alpha12"
|
tvFoundation = "1.0.0-alpha12"
|
||||||
tvMaterial = "1.0.1"
|
tvMaterial = "1.0.1"
|
||||||
lifecycleRuntimeKtx = "2.10.0"
|
lifecycleRuntimeKtx = "2.10.0"
|
||||||
activityCompose = "1.12.2"
|
activityCompose = "1.12.3"
|
||||||
androidx-media3 = "1.9.0"
|
androidx-media3 = "1.9.1"
|
||||||
coil = "3.3.0"
|
coil = "3.3.0"
|
||||||
jellyfin-sdk = "1.7.1"
|
jellyfin-sdk = "1.7.1"
|
||||||
nav3Core = "1.0.0"
|
nav3Core = "1.0.0"
|
||||||
|
|
@ -33,15 +33,15 @@ lifecycleViewmodelNav3 = "2.10.0"
|
||||||
material3AdaptiveNav3 = "1.0.0-alpha03"
|
material3AdaptiveNav3 = "1.0.0-alpha03"
|
||||||
protobuf = "0.9.6"
|
protobuf = "0.9.6"
|
||||||
datastore = "1.2.0"
|
datastore = "1.2.0"
|
||||||
kotlinx-serialization = "1.9.0"
|
kotlinx-serialization = "1.10.0"
|
||||||
protobuf-javalite = "4.33.2"
|
protobuf-javalite = "4.33.4"
|
||||||
hilt = "2.57.2"
|
hilt = "2.58"
|
||||||
room = "2.8.4"
|
room = "2.8.4"
|
||||||
preferenceKtx = "1.2.1"
|
preferenceKtx = "1.2.1"
|
||||||
tvprovider = "1.1.0"
|
tvprovider = "1.1.0"
|
||||||
workRuntimeKtx = "2.11.0"
|
workRuntimeKtx = "2.11.1"
|
||||||
paletteKtx = "1.0.0"
|
paletteKtx = "1.0.0"
|
||||||
openapi-generator = "7.18.0"
|
openapi-generator = "7.19.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
aboutlibraries-core = { module = "com.mikepenz:aboutlibraries-core", version.ref = "aboutLibraries" }
|
aboutlibraries-core = { module = "com.mikepenz:aboutlibraries-core", version.ref = "aboutLibraries" }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue