mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Set a different style for HDR subtitles & adjust image subtitles opacity (#827)
## Description Can now configure a different subtitle style for HDR playback. There is also a new setting to change the opacity/dim image based subtitles. This is primarily for HDR to dim very bright PGS subtitles, but the setting is available for SDR too. Please remember that the styles only apply to unstyled text formats like SRT. I haven't been able to consistently get the preview to switch the display into HDR mode, so the preview is not 100% accurate. ### Related issues Closes #583 ### Testing Shield pro 2019 w/ LG C2 ## AI or LLM usage None
This commit is contained in:
parent
b0b8ae3639
commit
ee440698b0
15 changed files with 475 additions and 86 deletions
|
|
@ -21,6 +21,7 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.RectangleShape
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
|
|
@ -57,6 +58,7 @@ import com.github.damontecres.wholphin.services.hilt.AuthOkHttpClient
|
||||||
import com.github.damontecres.wholphin.services.tvprovider.TvProviderSchedulerService
|
import com.github.damontecres.wholphin.services.tvprovider.TvProviderSchedulerService
|
||||||
import com.github.damontecres.wholphin.ui.CoilConfig
|
import com.github.damontecres.wholphin.ui.CoilConfig
|
||||||
import com.github.damontecres.wholphin.ui.LocalImageUrlService
|
import com.github.damontecres.wholphin.ui.LocalImageUrlService
|
||||||
|
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||||
import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisodeIds
|
import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisodeIds
|
||||||
import com.github.damontecres.wholphin.ui.launchIO
|
import com.github.damontecres.wholphin.ui.launchIO
|
||||||
import com.github.damontecres.wholphin.ui.nav.ApplicationContent
|
import com.github.damontecres.wholphin.ui.nav.ApplicationContent
|
||||||
|
|
@ -70,6 +72,7 @@ import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.firstOrNull
|
import kotlinx.coroutines.flow.firstOrNull
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
|
@ -133,7 +136,9 @@ class MainActivity : AppCompatActivity() {
|
||||||
Timber.i("MainActivity.onCreate: savedInstanceState is null=${savedInstanceState == null}")
|
Timber.i("MainActivity.onCreate: savedInstanceState is null=${savedInstanceState == null}")
|
||||||
lifecycle.addObserver(playbackLifecycleObserver)
|
lifecycle.addObserver(playbackLifecycleObserver)
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
appUpgradeHandler.copySubfont(false)
|
lifecycleScope.launchIO {
|
||||||
|
appUpgradeHandler.copySubfont(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
viewModel.serverRepository.currentUser.observe(this) { user ->
|
viewModel.serverRepository.currentUser.observe(this) { user ->
|
||||||
if (user?.hasPin == true) {
|
if (user?.hasPin == true) {
|
||||||
|
|
@ -148,6 +153,25 @@ class MainActivity : AppCompatActivity() {
|
||||||
viewModel.appStart()
|
viewModel.appStart()
|
||||||
setContent {
|
setContent {
|
||||||
val appPreferences by userPreferencesDataStore.data.collectAsState(null)
|
val appPreferences by userPreferencesDataStore.data.collectAsState(null)
|
||||||
|
if (appPreferences == null) {
|
||||||
|
// Show loading page if it is taking a while to get app preferences
|
||||||
|
var showLoading by remember { mutableStateOf(false) }
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
delay(500)
|
||||||
|
Timber.i("Showing loading page")
|
||||||
|
showLoading = true
|
||||||
|
}
|
||||||
|
if (showLoading) {
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color.Black),
|
||||||
|
) {
|
||||||
|
LoadingPage()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
appPreferences?.let { appPreferences ->
|
appPreferences?.let { appPreferences ->
|
||||||
LaunchedEffect(appPreferences.signInAutomatically) {
|
LaunchedEffect(appPreferences.signInAutomatically) {
|
||||||
signInAuto = appPreferences.signInAutomatically
|
signInAuto = appPreferences.signInAutomatically
|
||||||
|
|
|
||||||
|
|
@ -710,7 +710,7 @@ sealed interface AppPreference<Pref, T> {
|
||||||
val SubtitleStyle =
|
val SubtitleStyle =
|
||||||
AppDestinationPreference<AppPreferences>(
|
AppDestinationPreference<AppPreferences>(
|
||||||
title = R.string.subtitle_style,
|
title = R.string.subtitle_style,
|
||||||
destination = Destination.Settings(PreferenceScreenOption.SUBTITLES),
|
destination = Destination.SubtitleSettings(false),
|
||||||
)
|
)
|
||||||
|
|
||||||
val RefreshRateSwitching =
|
val RefreshRateSwitching =
|
||||||
|
|
@ -969,8 +969,6 @@ val basicPreferences =
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
val uiPreferences = listOf<PreferenceGroup>()
|
|
||||||
|
|
||||||
private val ExoPlayerSettings =
|
private val ExoPlayerSettings =
|
||||||
listOf(
|
listOf(
|
||||||
AppPreference.FfmpegPreference,
|
AppPreference.FfmpegPreference,
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,12 @@ class AppPreferencesSerializer
|
||||||
.apply {
|
.apply {
|
||||||
resetSubtitles()
|
resetSubtitles()
|
||||||
}.build()
|
}.build()
|
||||||
|
hdrSubtitlesPreferences =
|
||||||
|
SubtitlePreferences
|
||||||
|
.newBuilder()
|
||||||
|
.apply {
|
||||||
|
resetSubtitles()
|
||||||
|
}.build()
|
||||||
|
|
||||||
liveTvPreferences =
|
liveTvPreferences =
|
||||||
LiveTvPreferences
|
LiveTvPreferences
|
||||||
|
|
@ -193,4 +199,5 @@ fun SubtitlePreferences.Builder.resetSubtitles() {
|
||||||
backgroundStyle = SubtitleSettings.BackgroundStylePref.defaultValue
|
backgroundStyle = SubtitleSettings.BackgroundStylePref.defaultValue
|
||||||
margin = SubtitleSettings.Margin.defaultValue.toInt()
|
margin = SubtitleSettings.Margin.defaultValue.toInt()
|
||||||
edgeThickness = SubtitleSettings.EdgeThickness.defaultValue.toInt()
|
edgeThickness = SubtitleSettings.EdgeThickness.defaultValue.toInt()
|
||||||
|
imageSubtitleOpacity = SubtitleSettings.ImageOpacity.defaultValue.toInt()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -213,4 +213,19 @@ suspend fun upgradeApp(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (previous.isEqualOrBefore(Version.fromString("0.4.1-6-g0"))) {
|
||||||
|
appPreferences.updateData {
|
||||||
|
it.updateInterfacePreferences {
|
||||||
|
subtitlesPreferences =
|
||||||
|
subtitlesPreferences
|
||||||
|
.toBuilder()
|
||||||
|
.apply {
|
||||||
|
imageSubtitleOpacity = SubtitleSettings.ImageOpacity.defaultValue.toInt()
|
||||||
|
}.build()
|
||||||
|
// Copy current subtitle prefs as HDR ones
|
||||||
|
hdrSubtitlesPreferences = subtitlesPreferences.toBuilder().build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ class RefreshRateService
|
||||||
@param:ApplicationContext private val context: Context,
|
@param:ApplicationContext private val context: Context,
|
||||||
) {
|
) {
|
||||||
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
|
||||||
private val display = displayManager.getDisplay(Display.DEFAULT_DISPLAY)
|
private val display get() = displayManager.getDisplay(Display.DEFAULT_DISPLAY)
|
||||||
|
|
||||||
val supportedDisplayModes get() = display.supportedModes.orEmpty()
|
val supportedDisplayModes get() = display.supportedModes.orEmpty()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,11 @@ sealed class Destination(
|
||||||
val screen: PreferenceScreenOption,
|
val screen: PreferenceScreenOption,
|
||||||
) : Destination(true)
|
) : Destination(true)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SubtitleSettings(
|
||||||
|
val hdr: Boolean,
|
||||||
|
) : Destination(true)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object Search : Destination()
|
data object Search : Destination()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import com.github.damontecres.wholphin.ui.main.HomePage
|
||||||
import com.github.damontecres.wholphin.ui.main.SearchPage
|
import com.github.damontecres.wholphin.ui.main.SearchPage
|
||||||
import com.github.damontecres.wholphin.ui.playback.PlaybackPage
|
import com.github.damontecres.wholphin.ui.playback.PlaybackPage
|
||||||
import com.github.damontecres.wholphin.ui.preferences.PreferencesPage
|
import com.github.damontecres.wholphin.ui.preferences.PreferencesPage
|
||||||
|
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleStylePage
|
||||||
import com.github.damontecres.wholphin.ui.setup.InstallUpdatePage
|
import com.github.damontecres.wholphin.ui.setup.InstallUpdatePage
|
||||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
import org.jellyfin.sdk.model.api.CollectionType
|
import org.jellyfin.sdk.model.api.CollectionType
|
||||||
|
|
@ -78,6 +79,14 @@ fun DestinationContent(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is Destination.SubtitleSettings -> {
|
||||||
|
SubtitleStylePage(
|
||||||
|
preferences.appPreferences,
|
||||||
|
destination.hdr,
|
||||||
|
modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
is Destination.SeriesOverview -> {
|
is Destination.SeriesOverview -> {
|
||||||
SeriesOverview(
|
SeriesOverview(
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
|
@ -70,6 +71,7 @@ import com.github.damontecres.wholphin.ui.LocalImageUrlService
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||||
import com.github.damontecres.wholphin.ui.components.TextButton
|
import com.github.damontecres.wholphin.ui.components.TextButton
|
||||||
|
import com.github.damontecres.wholphin.ui.ifElse
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings.applyToMpv
|
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings.applyToMpv
|
||||||
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings.calculateEdgeSize
|
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings.calculateEdgeSize
|
||||||
|
|
@ -85,6 +87,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jellyfin.sdk.model.extensions.ticks
|
import org.jellyfin.sdk.model.extensions.ticks
|
||||||
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
@ -176,6 +179,7 @@ fun PlaybackPageContent(
|
||||||
LaunchedEffect(player) {
|
LaunchedEffect(player) {
|
||||||
if (playerBackend == PlayerBackend.MPV) {
|
if (playerBackend == PlayerBackend.MPV) {
|
||||||
scope.launch(Dispatchers.IO + ExceptionHandler()) {
|
scope.launch(Dispatchers.IO + ExceptionHandler()) {
|
||||||
|
// MPV can't play HDR, so always use regular settings
|
||||||
preferences.appPreferences.interfacePreferences.subtitlesPreferences.applyToMpv(
|
preferences.appPreferences.interfacePreferences.subtitlesPreferences.applyToMpv(
|
||||||
configuration,
|
configuration,
|
||||||
density,
|
density,
|
||||||
|
|
@ -401,13 +405,26 @@ fun PlaybackPageContent(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val subtitleSettings =
|
||||||
|
remember(mediaInfo) {
|
||||||
|
Timber.v("subtitle choice: ${mediaInfo?.videoStream?.hdr}")
|
||||||
|
if (mediaInfo?.videoStream?.hdr == true) {
|
||||||
|
preferences.appPreferences.interfacePreferences.hdrSubtitlesPreferences
|
||||||
|
} else {
|
||||||
|
preferences.appPreferences.interfacePreferences.subtitlesPreferences
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val subtitleImageOpacity =
|
||||||
|
remember(subtitleSettings) { subtitleSettings.imageSubtitleOpacity / 100f }
|
||||||
|
|
||||||
// Subtitles
|
// Subtitles
|
||||||
if (skipIndicatorDuration == 0L && currentItemPlayback.subtitleIndexEnabled) {
|
if (skipIndicatorDuration == 0L && currentItemPlayback.subtitleIndexEnabled) {
|
||||||
val maxSize by animateFloatAsState(if (controllerViewState.controlsVisible) .7f else 1f)
|
val maxSize by animateFloatAsState(if (controllerViewState.controlsVisible) .7f else 1f)
|
||||||
|
val isImageSubtitles = remember(cues) { cues.firstOrNull()?.bitmap != null }
|
||||||
AndroidView(
|
AndroidView(
|
||||||
factory = { context ->
|
factory = { context ->
|
||||||
SubtitleView(context).apply {
|
SubtitleView(context).apply {
|
||||||
preferences.appPreferences.interfacePreferences.subtitlesPreferences.let {
|
subtitleSettings.let {
|
||||||
setStyle(it.toSubtitleStyle())
|
setStyle(it.toSubtitleStyle())
|
||||||
setFixedTextSize(Dimension.SP, it.fontSize.toFloat())
|
setFixedTextSize(Dimension.SP, it.fontSize.toFloat())
|
||||||
setBottomPaddingFraction(it.margin.toFloat() / 100f)
|
setBottomPaddingFraction(it.margin.toFloat() / 100f)
|
||||||
|
|
@ -416,10 +433,8 @@ fun PlaybackPageContent(
|
||||||
},
|
},
|
||||||
update = {
|
update = {
|
||||||
it.setCues(cues)
|
it.setCues(cues)
|
||||||
Media3SubtitleOverride(
|
Media3SubtitleOverride(subtitleSettings.calculateEdgeSize(density))
|
||||||
preferences.appPreferences.interfacePreferences.subtitlesPreferences
|
.apply(it)
|
||||||
.calculateEdgeSize(density),
|
|
||||||
).apply(it)
|
|
||||||
},
|
},
|
||||||
onReset = {
|
onReset = {
|
||||||
it.setCues(null)
|
it.setCues(null)
|
||||||
|
|
@ -428,7 +443,8 @@ fun PlaybackPageContent(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize(maxSize)
|
.fillMaxSize(maxSize)
|
||||||
.align(Alignment.TopCenter)
|
.align(Alignment.TopCenter)
|
||||||
.background(Color.Transparent),
|
.background(Color.Transparent)
|
||||||
|
.ifElse(isImageSubtitles, Modifier.alpha(subtitleImageOpacity)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,20 @@ package com.github.damontecres.wholphin.ui.preferences
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import com.github.damontecres.wholphin.preferences.AppPreference
|
import com.github.damontecres.wholphin.preferences.AppPreference
|
||||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A group of preferences
|
* A group of preferences
|
||||||
*/
|
*/
|
||||||
data class PreferenceGroup(
|
data class PreferenceGroup<T>(
|
||||||
@param:StringRes val title: Int,
|
@param:StringRes val title: Int,
|
||||||
val preferences: List<AppPreference<AppPreferences, out Any?>>,
|
val preferences: List<AppPreference<T, out Any?>>,
|
||||||
val conditionalPreferences: List<ConditionalPreferences> = listOf(),
|
val conditionalPreferences: List<ConditionalPreferences<T>> = listOf(),
|
||||||
)
|
)
|
||||||
|
|
||||||
data class ConditionalPreferences(
|
data class ConditionalPreferences<T>(
|
||||||
val condition: (AppPreferences) -> Boolean,
|
val condition: (T) -> Boolean,
|
||||||
val preferences: List<AppPreference<AppPreferences, out Any?>>,
|
val preferences: List<AppPreference<T, out Any?>>,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -34,8 +33,6 @@ sealed interface PreferenceValidation {
|
||||||
enum class PreferenceScreenOption {
|
enum class PreferenceScreenOption {
|
||||||
BASIC,
|
BASIC,
|
||||||
ADVANCED,
|
ADVANCED,
|
||||||
USER_INTERFACE,
|
|
||||||
SUBTITLES,
|
|
||||||
EXO_PLAYER,
|
EXO_PLAYER,
|
||||||
MPV,
|
MPV,
|
||||||
;
|
;
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,6 @@ import com.github.damontecres.wholphin.preferences.MpvPreferences
|
||||||
import com.github.damontecres.wholphin.preferences.PlayerBackend
|
import com.github.damontecres.wholphin.preferences.PlayerBackend
|
||||||
import com.github.damontecres.wholphin.preferences.advancedPreferences
|
import com.github.damontecres.wholphin.preferences.advancedPreferences
|
||||||
import com.github.damontecres.wholphin.preferences.basicPreferences
|
import com.github.damontecres.wholphin.preferences.basicPreferences
|
||||||
import com.github.damontecres.wholphin.preferences.uiPreferences
|
|
||||||
import com.github.damontecres.wholphin.preferences.updatePlaybackPreferences
|
import com.github.damontecres.wholphin.preferences.updatePlaybackPreferences
|
||||||
import com.github.damontecres.wholphin.services.UpdateChecker
|
import com.github.damontecres.wholphin.services.UpdateChecker
|
||||||
import com.github.damontecres.wholphin.ui.components.ConfirmDialog
|
import com.github.damontecres.wholphin.ui.components.ConfirmDialog
|
||||||
|
|
@ -60,7 +59,6 @@ import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.playOnClickSound
|
import com.github.damontecres.wholphin.ui.playOnClickSound
|
||||||
import com.github.damontecres.wholphin.ui.playSoundOnFocus
|
import com.github.damontecres.wholphin.ui.playSoundOnFocus
|
||||||
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings
|
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings
|
||||||
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleStylePage
|
|
||||||
import com.github.damontecres.wholphin.ui.setup.UpdateViewModel
|
import com.github.damontecres.wholphin.ui.setup.UpdateViewModel
|
||||||
import com.github.damontecres.wholphin.ui.setup.seerr.AddSeerServerDialog
|
import com.github.damontecres.wholphin.ui.setup.seerr.AddSeerServerDialog
|
||||||
import com.github.damontecres.wholphin.ui.setup.seerr.SwitchSeerrViewModel
|
import com.github.damontecres.wholphin.ui.setup.seerr.SwitchSeerrViewModel
|
||||||
|
|
@ -125,8 +123,6 @@ fun PreferencesContent(
|
||||||
when (preferenceScreenOption) {
|
when (preferenceScreenOption) {
|
||||||
PreferenceScreenOption.BASIC -> basicPreferences
|
PreferenceScreenOption.BASIC -> basicPreferences
|
||||||
PreferenceScreenOption.ADVANCED -> advancedPreferences
|
PreferenceScreenOption.ADVANCED -> advancedPreferences
|
||||||
PreferenceScreenOption.USER_INTERFACE -> uiPreferences
|
|
||||||
PreferenceScreenOption.SUBTITLES -> SubtitleSettings.preferences
|
|
||||||
PreferenceScreenOption.EXO_PLAYER -> ExoPlayerPreferences
|
PreferenceScreenOption.EXO_PLAYER -> ExoPlayerPreferences
|
||||||
PreferenceScreenOption.MPV -> MpvPreferences
|
PreferenceScreenOption.MPV -> MpvPreferences
|
||||||
}
|
}
|
||||||
|
|
@ -134,8 +130,6 @@ fun PreferencesContent(
|
||||||
when (preferenceScreenOption) {
|
when (preferenceScreenOption) {
|
||||||
PreferenceScreenOption.BASIC -> R.string.settings
|
PreferenceScreenOption.BASIC -> R.string.settings
|
||||||
PreferenceScreenOption.ADVANCED -> R.string.advanced_settings
|
PreferenceScreenOption.ADVANCED -> R.string.advanced_settings
|
||||||
PreferenceScreenOption.USER_INTERFACE -> R.string.ui_interface
|
|
||||||
PreferenceScreenOption.SUBTITLES -> R.string.subtitle_style
|
|
||||||
PreferenceScreenOption.EXO_PLAYER -> R.string.exoplayer_options
|
PreferenceScreenOption.EXO_PLAYER -> R.string.exoplayer_options
|
||||||
PreferenceScreenOption.MPV -> R.string.mpv_options
|
PreferenceScreenOption.MPV -> R.string.mpv_options
|
||||||
}
|
}
|
||||||
|
|
@ -526,7 +520,6 @@ fun PreferencesPage(
|
||||||
when (preferenceScreenOption) {
|
when (preferenceScreenOption) {
|
||||||
PreferenceScreenOption.BASIC,
|
PreferenceScreenOption.BASIC,
|
||||||
PreferenceScreenOption.ADVANCED,
|
PreferenceScreenOption.ADVANCED,
|
||||||
PreferenceScreenOption.USER_INTERFACE,
|
|
||||||
PreferenceScreenOption.EXO_PLAYER,
|
PreferenceScreenOption.EXO_PLAYER,
|
||||||
PreferenceScreenOption.MPV,
|
PreferenceScreenOption.MPV,
|
||||||
-> {
|
-> {
|
||||||
|
|
@ -539,12 +532,6 @@ fun PreferencesPage(
|
||||||
.align(Alignment.TopEnd),
|
.align(Alignment.TopEnd),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
PreferenceScreenOption.SUBTITLES -> {
|
|
||||||
SubtitleStylePage(
|
|
||||||
initialPreferences,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,195 @@
|
||||||
|
package com.github.damontecres.wholphin.ui.preferences.subtitle
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.slideInHorizontally
|
||||||
|
import androidx.compose.animation.slideOutHorizontally
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
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.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
import androidx.tv.material3.MaterialTheme
|
||||||
|
import androidx.tv.material3.Text
|
||||||
|
import androidx.tv.material3.surfaceColorAtElevation
|
||||||
|
import com.github.damontecres.wholphin.preferences.AppPreference
|
||||||
|
import com.github.damontecres.wholphin.preferences.SubtitlePreferences
|
||||||
|
import com.github.damontecres.wholphin.preferences.resetSubtitles
|
||||||
|
import com.github.damontecres.wholphin.ui.ifElse
|
||||||
|
import com.github.damontecres.wholphin.ui.preferences.ClickPreference
|
||||||
|
import com.github.damontecres.wholphin.ui.preferences.ComposablePreference
|
||||||
|
import com.github.damontecres.wholphin.ui.preferences.PreferenceGroup
|
||||||
|
import com.github.damontecres.wholphin.ui.preferences.PreferenceValidation
|
||||||
|
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
|
||||||
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SubtitlePreferencesContent(
|
||||||
|
title: String,
|
||||||
|
preferences: SubtitlePreferences,
|
||||||
|
prefList: List<PreferenceGroup<SubtitlePreferences>>,
|
||||||
|
onPreferenceChange: suspend (SubtitlePreferences) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
viewModel: PreferencesViewModel = hiltViewModel(),
|
||||||
|
onFocus: (Int, Int) -> Unit = { _, _ -> },
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
var focusedIndex by rememberSaveable { mutableStateOf(Pair(0, 0)) }
|
||||||
|
val state = rememberLazyListState()
|
||||||
|
|
||||||
|
var visible by remember { mutableStateOf(false) }
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
// Forces the animated to trigger
|
||||||
|
visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = visible,
|
||||||
|
enter = fadeIn() + slideInHorizontally { it / 2 },
|
||||||
|
exit = fadeOut() + slideOutHorizontally { it / 2 },
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
focusRequester.tryRequestFocus()
|
||||||
|
}
|
||||||
|
LazyColumn(
|
||||||
|
state = state,
|
||||||
|
horizontalAlignment = Alignment.Start,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(0.dp),
|
||||||
|
contentPadding = PaddingValues(16.dp),
|
||||||
|
modifier = Modifier.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)),
|
||||||
|
) {
|
||||||
|
stickyHeader {
|
||||||
|
Text(
|
||||||
|
text = title,
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 8.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
prefList.forEachIndexed { groupIndex, group ->
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
text = stringResource(group.title),
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
textAlign = TextAlign.Start,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 8.dp, bottom = 4.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val groupPreferences =
|
||||||
|
group.preferences +
|
||||||
|
group.conditionalPreferences
|
||||||
|
.filter { it.condition.invoke(preferences) }
|
||||||
|
.map { it.preferences }
|
||||||
|
.flatten()
|
||||||
|
groupPreferences.forEachIndexed { prefIndex, pref ->
|
||||||
|
pref as AppPreference<SubtitlePreferences, Any>
|
||||||
|
item {
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val focused = interactionSource.collectIsFocusedAsState().value
|
||||||
|
LaunchedEffect(focused) {
|
||||||
|
if (focused) {
|
||||||
|
focusedIndex = Pair(groupIndex, prefIndex)
|
||||||
|
onFocus.invoke(groupIndex, prefIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
when (pref) {
|
||||||
|
SubtitleSettings.Reset -> {
|
||||||
|
ClickPreference(
|
||||||
|
title = stringResource(pref.title),
|
||||||
|
onClick = {
|
||||||
|
scope.launch(ExceptionHandler()) {
|
||||||
|
val newValue =
|
||||||
|
SubtitlePreferences
|
||||||
|
.newBuilder()
|
||||||
|
.apply { resetSubtitles() }
|
||||||
|
.build()
|
||||||
|
onPreferenceChange.invoke(newValue)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
val value = pref.getter.invoke(preferences)
|
||||||
|
ComposablePreference(
|
||||||
|
preference = pref,
|
||||||
|
value = value,
|
||||||
|
onNavigate = viewModel.navigationManager::navigateTo,
|
||||||
|
onValueChange = { newValue ->
|
||||||
|
val validation = pref.validate(newValue)
|
||||||
|
when (validation) {
|
||||||
|
is PreferenceValidation.Invalid -> {
|
||||||
|
// TODO?
|
||||||
|
Toast
|
||||||
|
.makeText(
|
||||||
|
context,
|
||||||
|
validation.message,
|
||||||
|
Toast.LENGTH_SHORT,
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
PreferenceValidation.Valid -> {
|
||||||
|
scope.launch(ExceptionHandler()) {
|
||||||
|
onPreferenceChange.invoke(
|
||||||
|
pref.setter(
|
||||||
|
preferences,
|
||||||
|
newValue,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.ifElse(
|
||||||
|
groupIndex == focusedIndex.first && prefIndex == focusedIndex.second,
|
||||||
|
Modifier.focusRequester(focusRequester),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,8 @@ package com.github.damontecres.wholphin.ui.preferences.subtitle
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
|
import android.os.Build
|
||||||
|
import android.view.Display
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.toArgb
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
|
@ -13,14 +15,14 @@ import androidx.media3.ui.CaptionStyleCompat
|
||||||
import com.github.damontecres.wholphin.R
|
import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.preferences.AppChoicePreference
|
import com.github.damontecres.wholphin.preferences.AppChoicePreference
|
||||||
import com.github.damontecres.wholphin.preferences.AppClickablePreference
|
import com.github.damontecres.wholphin.preferences.AppClickablePreference
|
||||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
import com.github.damontecres.wholphin.preferences.AppDestinationPreference
|
||||||
import com.github.damontecres.wholphin.preferences.AppSliderPreference
|
import com.github.damontecres.wholphin.preferences.AppSliderPreference
|
||||||
import com.github.damontecres.wholphin.preferences.AppSwitchPreference
|
import com.github.damontecres.wholphin.preferences.AppSwitchPreference
|
||||||
import com.github.damontecres.wholphin.preferences.BackgroundStyle
|
import com.github.damontecres.wholphin.preferences.BackgroundStyle
|
||||||
import com.github.damontecres.wholphin.preferences.EdgeStyle
|
import com.github.damontecres.wholphin.preferences.EdgeStyle
|
||||||
import com.github.damontecres.wholphin.preferences.SubtitlePreferences
|
import com.github.damontecres.wholphin.preferences.SubtitlePreferences
|
||||||
import com.github.damontecres.wholphin.preferences.updateSubtitlePreferences
|
|
||||||
import com.github.damontecres.wholphin.ui.indexOfFirstOrNull
|
import com.github.damontecres.wholphin.ui.indexOfFirstOrNull
|
||||||
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.preferences.PreferenceGroup
|
import com.github.damontecres.wholphin.ui.preferences.PreferenceGroup
|
||||||
import com.github.damontecres.wholphin.util.mpv.MPVLib
|
import com.github.damontecres.wholphin.util.mpv.MPVLib
|
||||||
import com.github.damontecres.wholphin.util.mpv.setPropertyColor
|
import com.github.damontecres.wholphin.util.mpv.setPropertyColor
|
||||||
|
|
@ -28,18 +30,17 @@ import timber.log.Timber
|
||||||
|
|
||||||
object SubtitleSettings {
|
object SubtitleSettings {
|
||||||
val FontSize =
|
val FontSize =
|
||||||
AppSliderPreference<AppPreferences>(
|
AppSliderPreference<SubtitlePreferences>(
|
||||||
title = R.string.font_size,
|
title = R.string.font_size,
|
||||||
defaultValue = 24,
|
defaultValue = 24,
|
||||||
min = 8,
|
min = 8,
|
||||||
max = 70,
|
max = 70,
|
||||||
interval = 2,
|
interval = 2,
|
||||||
getter = {
|
getter = {
|
||||||
it.interfacePreferences.subtitlesPreferences.fontSize
|
it.fontSize.toLong()
|
||||||
.toLong()
|
|
||||||
},
|
},
|
||||||
setter = { prefs, value ->
|
setter = { prefs, value ->
|
||||||
prefs.updateSubtitlePreferences { fontSize = value.toInt() }
|
prefs.update { fontSize = value.toInt() }
|
||||||
},
|
},
|
||||||
summarizer = { value -> value?.toString() },
|
summarizer = { value -> value?.toString() },
|
||||||
)
|
)
|
||||||
|
|
@ -59,12 +60,12 @@ object SubtitleSettings {
|
||||||
)
|
)
|
||||||
|
|
||||||
val FontColor =
|
val FontColor =
|
||||||
AppChoicePreference<AppPreferences, Color>(
|
AppChoicePreference<SubtitlePreferences, Color>(
|
||||||
title = R.string.font_color,
|
title = R.string.font_color,
|
||||||
defaultValue = Color.White,
|
defaultValue = Color.White,
|
||||||
getter = { Color(it.interfacePreferences.subtitlesPreferences.fontColor) },
|
getter = { Color(it.fontColor) },
|
||||||
setter = { prefs, value ->
|
setter = { prefs, value ->
|
||||||
prefs.updateSubtitlePreferences { fontColor = value.toArgb().and(0x00FFFFFF) }
|
prefs.update { fontColor = value.toArgb().and(0x00FFFFFF) }
|
||||||
},
|
},
|
||||||
displayValues = R.array.font_colors,
|
displayValues = R.array.font_colors,
|
||||||
indexToValue = { colorList.getOrNull(it) ?: Color.White },
|
indexToValue = { colorList.getOrNull(it) ?: Color.White },
|
||||||
|
|
@ -75,49 +76,49 @@ object SubtitleSettings {
|
||||||
)
|
)
|
||||||
|
|
||||||
val FontBold =
|
val FontBold =
|
||||||
AppSwitchPreference<AppPreferences>(
|
AppSwitchPreference<SubtitlePreferences>(
|
||||||
title = R.string.bold_font,
|
title = R.string.bold_font,
|
||||||
defaultValue = false,
|
defaultValue = false,
|
||||||
getter = { it.interfacePreferences.subtitlesPreferences.fontBold },
|
getter = { it.fontBold },
|
||||||
setter = { prefs, value ->
|
setter = { prefs, value ->
|
||||||
prefs.updateSubtitlePreferences { fontBold = value }
|
prefs.update { fontBold = value }
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
val FontItalic =
|
val FontItalic =
|
||||||
AppSwitchPreference<AppPreferences>(
|
AppSwitchPreference<SubtitlePreferences>(
|
||||||
title = R.string.italic_font,
|
title = R.string.italic_font,
|
||||||
defaultValue = false,
|
defaultValue = false,
|
||||||
getter = { it.interfacePreferences.subtitlesPreferences.fontItalic },
|
getter = { it.fontItalic },
|
||||||
setter = { prefs, value ->
|
setter = { prefs, value ->
|
||||||
prefs.updateSubtitlePreferences { fontItalic = value }
|
prefs.update { fontItalic = value }
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
val FontOpacity =
|
val FontOpacity =
|
||||||
AppSliderPreference<AppPreferences>(
|
AppSliderPreference<SubtitlePreferences>(
|
||||||
title = R.string.font_opacity,
|
title = R.string.font_opacity,
|
||||||
defaultValue = 100,
|
defaultValue = 100,
|
||||||
min = 10,
|
min = 10,
|
||||||
max = 100,
|
max = 100,
|
||||||
interval = 10,
|
interval = 10,
|
||||||
getter = {
|
getter = {
|
||||||
it.interfacePreferences.subtitlesPreferences.fontOpacity
|
it.fontOpacity
|
||||||
.toLong()
|
.toLong()
|
||||||
},
|
},
|
||||||
setter = { prefs, value ->
|
setter = { prefs, value ->
|
||||||
prefs.updateSubtitlePreferences { fontOpacity = value.toInt() }
|
prefs.update { fontOpacity = value.toInt() }
|
||||||
},
|
},
|
||||||
summarizer = { value -> value?.let { "$it%" } },
|
summarizer = { value -> value?.let { "$it%" } },
|
||||||
)
|
)
|
||||||
|
|
||||||
val EdgeStylePref =
|
val EdgeStylePref =
|
||||||
AppChoicePreference<AppPreferences, EdgeStyle>(
|
AppChoicePreference<SubtitlePreferences, EdgeStyle>(
|
||||||
title =
|
title =
|
||||||
R.string.edge_style,
|
R.string.edge_style,
|
||||||
defaultValue = EdgeStyle.EDGE_SOLID,
|
defaultValue = EdgeStyle.EDGE_SOLID,
|
||||||
getter = { it.interfacePreferences.subtitlesPreferences.edgeStyle },
|
getter = { it.edgeStyle },
|
||||||
setter = { prefs, value ->
|
setter = { prefs, value ->
|
||||||
prefs.updateSubtitlePreferences { edgeStyle = value }
|
prefs.update { edgeStyle = value }
|
||||||
},
|
},
|
||||||
displayValues = R.array.subtitle_edge,
|
displayValues = R.array.subtitle_edge,
|
||||||
indexToValue = { EdgeStyle.forNumber(it) },
|
indexToValue = { EdgeStyle.forNumber(it) },
|
||||||
|
|
@ -125,12 +126,12 @@ object SubtitleSettings {
|
||||||
)
|
)
|
||||||
|
|
||||||
val EdgeColor =
|
val EdgeColor =
|
||||||
AppChoicePreference<AppPreferences, Color>(
|
AppChoicePreference<SubtitlePreferences, Color>(
|
||||||
title = R.string.edge_color,
|
title = R.string.edge_color,
|
||||||
defaultValue = Color.Black,
|
defaultValue = Color.Black,
|
||||||
getter = { Color(it.interfacePreferences.subtitlesPreferences.edgeColor) },
|
getter = { Color(it.edgeColor) },
|
||||||
setter = { prefs, value ->
|
setter = { prefs, value ->
|
||||||
prefs.updateSubtitlePreferences { edgeColor = value.toArgb().and(0x00FFFFFF) }
|
prefs.update { edgeColor = value.toArgb().and(0x00FFFFFF) }
|
||||||
},
|
},
|
||||||
displayValues = R.array.font_colors,
|
displayValues = R.array.font_colors,
|
||||||
indexToValue = { colorList.getOrNull(it) ?: Color.White },
|
indexToValue = { colorList.getOrNull(it) ?: Color.White },
|
||||||
|
|
@ -141,29 +142,29 @@ object SubtitleSettings {
|
||||||
)
|
)
|
||||||
|
|
||||||
val EdgeThickness =
|
val EdgeThickness =
|
||||||
AppSliderPreference<AppPreferences>(
|
AppSliderPreference<SubtitlePreferences>(
|
||||||
title = R.string.edge_size,
|
title = R.string.edge_size,
|
||||||
defaultValue = 4,
|
defaultValue = 4,
|
||||||
min = 1,
|
min = 1,
|
||||||
max = 32,
|
max = 32,
|
||||||
interval = 1,
|
interval = 1,
|
||||||
getter = {
|
getter = {
|
||||||
it.interfacePreferences.subtitlesPreferences.edgeThickness
|
it.edgeThickness
|
||||||
.toLong()
|
.toLong()
|
||||||
},
|
},
|
||||||
setter = { prefs, value ->
|
setter = { prefs, value ->
|
||||||
prefs.updateSubtitlePreferences { edgeThickness = value.toInt() }
|
prefs.update { edgeThickness = value.toInt() }
|
||||||
},
|
},
|
||||||
summarizer = { value -> value?.let { "${it / 2.0}" } },
|
summarizer = { value -> value?.let { "${it / 2.0}" } },
|
||||||
)
|
)
|
||||||
|
|
||||||
val BackgroundColor =
|
val BackgroundColor =
|
||||||
AppChoicePreference<AppPreferences, Color>(
|
AppChoicePreference<SubtitlePreferences, Color>(
|
||||||
title = R.string.background_color,
|
title = R.string.background_color,
|
||||||
defaultValue = Color.Transparent,
|
defaultValue = Color.Transparent,
|
||||||
getter = { Color(it.interfacePreferences.subtitlesPreferences.backgroundColor) },
|
getter = { Color(it.backgroundColor) },
|
||||||
setter = { prefs, value ->
|
setter = { prefs, value ->
|
||||||
prefs.updateSubtitlePreferences { backgroundColor = value.toArgb().and(0x00FFFFFF) }
|
prefs.update { backgroundColor = value.toArgb().and(0x00FFFFFF) }
|
||||||
},
|
},
|
||||||
displayValues = R.array.font_colors,
|
displayValues = R.array.font_colors,
|
||||||
indexToValue = { colorList.getOrNull(it) ?: Color.White },
|
indexToValue = { colorList.getOrNull(it) ?: Color.White },
|
||||||
|
|
@ -174,30 +175,30 @@ object SubtitleSettings {
|
||||||
)
|
)
|
||||||
|
|
||||||
val BackgroundOpacity =
|
val BackgroundOpacity =
|
||||||
AppSliderPreference<AppPreferences>(
|
AppSliderPreference<SubtitlePreferences>(
|
||||||
title = R.string.background_opacity,
|
title = R.string.background_opacity,
|
||||||
defaultValue = 50,
|
defaultValue = 50,
|
||||||
min = 10,
|
min = 10,
|
||||||
max = 100,
|
max = 100,
|
||||||
interval = 10,
|
interval = 10,
|
||||||
getter = {
|
getter = {
|
||||||
it.interfacePreferences.subtitlesPreferences.backgroundOpacity
|
it.backgroundOpacity
|
||||||
.toLong()
|
.toLong()
|
||||||
},
|
},
|
||||||
setter = { prefs, value ->
|
setter = { prefs, value ->
|
||||||
prefs.updateSubtitlePreferences { backgroundOpacity = value.toInt() }
|
prefs.update { backgroundOpacity = value.toInt() }
|
||||||
},
|
},
|
||||||
summarizer = { value -> value?.let { "$it%" } },
|
summarizer = { value -> value?.let { "$it%" } },
|
||||||
)
|
)
|
||||||
|
|
||||||
val BackgroundStylePref =
|
val BackgroundStylePref =
|
||||||
AppChoicePreference<AppPreferences, BackgroundStyle>(
|
AppChoicePreference<SubtitlePreferences, BackgroundStyle>(
|
||||||
title =
|
title =
|
||||||
R.string.background_style,
|
R.string.background_style,
|
||||||
defaultValue = BackgroundStyle.BG_NONE,
|
defaultValue = BackgroundStyle.BG_NONE,
|
||||||
getter = { it.interfacePreferences.subtitlesPreferences.backgroundStyle },
|
getter = { it.backgroundStyle },
|
||||||
setter = { prefs, value ->
|
setter = { prefs, value ->
|
||||||
prefs.updateSubtitlePreferences { backgroundStyle = value }
|
prefs.update { backgroundStyle = value }
|
||||||
},
|
},
|
||||||
displayValues = R.array.background_style,
|
displayValues = R.array.background_style,
|
||||||
indexToValue = { BackgroundStyle.forNumber(it) },
|
indexToValue = { BackgroundStyle.forNumber(it) },
|
||||||
|
|
@ -205,29 +206,51 @@ object SubtitleSettings {
|
||||||
)
|
)
|
||||||
|
|
||||||
val Margin =
|
val Margin =
|
||||||
AppSliderPreference<AppPreferences>(
|
AppSliderPreference<SubtitlePreferences>(
|
||||||
title = R.string.subtitle_margin,
|
title = R.string.subtitle_margin,
|
||||||
defaultValue = 8,
|
defaultValue = 8,
|
||||||
min = 0,
|
min = 0,
|
||||||
max = 100,
|
max = 100,
|
||||||
interval = 1,
|
interval = 1,
|
||||||
getter = {
|
getter = {
|
||||||
it.interfacePreferences.subtitlesPreferences.margin
|
it.margin
|
||||||
.toLong()
|
.toLong()
|
||||||
},
|
},
|
||||||
setter = { prefs, value ->
|
setter = { prefs, value ->
|
||||||
prefs.updateSubtitlePreferences { margin = value.toInt() }
|
prefs.update { margin = value.toInt() }
|
||||||
|
},
|
||||||
|
summarizer = { value -> value?.let { "$it%" } },
|
||||||
|
)
|
||||||
|
|
||||||
|
val ImageOpacity =
|
||||||
|
AppSliderPreference<SubtitlePreferences>(
|
||||||
|
title = R.string.image_subtitle_opacity,
|
||||||
|
defaultValue = 100,
|
||||||
|
min = 10,
|
||||||
|
max = 100,
|
||||||
|
interval = 5,
|
||||||
|
getter = {
|
||||||
|
it.imageSubtitleOpacity.toLong()
|
||||||
|
},
|
||||||
|
setter = { prefs, value ->
|
||||||
|
prefs.update { imageSubtitleOpacity = value.toInt() }
|
||||||
},
|
},
|
||||||
summarizer = { value -> value?.let { "$it%" } },
|
summarizer = { value -> value?.let { "$it%" } },
|
||||||
)
|
)
|
||||||
|
|
||||||
val Reset =
|
val Reset =
|
||||||
AppClickablePreference<AppPreferences>(
|
AppClickablePreference<SubtitlePreferences>(
|
||||||
title = R.string.reset,
|
title = R.string.reset,
|
||||||
getter = { },
|
getter = { },
|
||||||
setter = { prefs, _ -> prefs },
|
setter = { prefs, _ -> prefs },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val HdrSettings =
|
||||||
|
AppDestinationPreference<SubtitlePreferences>(
|
||||||
|
title = R.string.hdr_subtitle_style,
|
||||||
|
destination = Destination.SubtitleSettings(true),
|
||||||
|
)
|
||||||
|
|
||||||
val preferences =
|
val preferences =
|
||||||
listOf(
|
listOf(
|
||||||
PreferenceGroup(
|
PreferenceGroup(
|
||||||
|
|
@ -264,11 +287,25 @@ object SubtitleSettings {
|
||||||
preferences =
|
preferences =
|
||||||
listOf(
|
listOf(
|
||||||
Margin,
|
Margin,
|
||||||
|
ImageOpacity,
|
||||||
Reset,
|
Reset,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val hdrPreferenceGroup =
|
||||||
|
listOf(
|
||||||
|
PreferenceGroup(
|
||||||
|
title = R.string.hdr,
|
||||||
|
preferences =
|
||||||
|
listOf(
|
||||||
|
HdrSettings,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun shouldShowHdr(display: Display): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && display.isHdr
|
||||||
|
|
||||||
private fun combine(
|
private fun combine(
|
||||||
color: Int,
|
color: Int,
|
||||||
opacity: Int,
|
opacity: Int,
|
||||||
|
|
@ -361,3 +398,5 @@ object SubtitleSettings {
|
||||||
MPVLib.setPropertyString("sub-border-style", borderStyle)
|
MPVLib.setPropertyString("sub-border-style", borderStyle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun SubtitlePreferences.update(block: SubtitlePreferences.Builder.() -> Unit): SubtitlePreferences = toBuilder().apply(block).build()
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package com.github.damontecres.wholphin.ui.preferences.subtitle
|
package com.github.damontecres.wholphin.ui.preferences.subtitle
|
||||||
|
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
|
import android.os.Build
|
||||||
import androidx.annotation.Dimension
|
import androidx.annotation.Dimension
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
|
@ -12,6 +14,7 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
|
@ -19,9 +22,13 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
|
@ -30,20 +37,25 @@ import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.ui.SubtitleView
|
import androidx.media3.ui.SubtitleView
|
||||||
import com.github.damontecres.wholphin.R
|
import com.github.damontecres.wholphin.R
|
||||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||||
import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
|
import com.github.damontecres.wholphin.preferences.SubtitlePreferences
|
||||||
import com.github.damontecres.wholphin.ui.preferences.PreferencesContent
|
import com.github.damontecres.wholphin.preferences.resetSubtitles
|
||||||
|
import com.github.damontecres.wholphin.preferences.updateInterfacePreferences
|
||||||
|
import com.github.damontecres.wholphin.ui.findActivity
|
||||||
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.calculateEdgeSize
|
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings.calculateEdgeSize
|
||||||
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings.toSubtitleStyle
|
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings.toSubtitleStyle
|
||||||
import com.github.damontecres.wholphin.util.Media3SubtitleOverride
|
import com.github.damontecres.wholphin.util.Media3SubtitleOverride
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SubtitleStylePage(
|
fun SubtitleStylePage(
|
||||||
initialPreferences: AppPreferences,
|
initialPreferences: AppPreferences,
|
||||||
|
hdrSettings: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: PreferencesViewModel = hiltViewModel(),
|
viewModel: PreferencesViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
var preferences by remember { mutableStateOf(initialPreferences) }
|
var preferences by remember { mutableStateOf(initialPreferences) }
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
|
@ -51,8 +63,27 @@ fun SubtitleStylePage(
|
||||||
preferences = it
|
preferences = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val prefs = preferences.interfacePreferences.subtitlesPreferences
|
val display = LocalView.current.display
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
DisposableEffect(context) {
|
||||||
|
if (hdrSettings) {
|
||||||
|
Timber.v("Switching color mode to HDR")
|
||||||
|
context.findActivity()?.window?.colorMode = ActivityInfo.COLOR_MODE_HDR
|
||||||
|
}
|
||||||
|
onDispose {
|
||||||
|
context.findActivity()?.window?.colorMode = ActivityInfo.COLOR_MODE_DEFAULT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val prefs =
|
||||||
|
if (hdrSettings) {
|
||||||
|
preferences.interfacePreferences.hdrSubtitlesPreferences
|
||||||
|
} else {
|
||||||
|
preferences.interfacePreferences.subtitlesPreferences
|
||||||
|
}
|
||||||
var focusedOnMargin by remember { mutableStateOf(false) }
|
var focusedOnMargin by remember { mutableStateOf(false) }
|
||||||
|
var focusedOnImageOpacity by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
|
@ -72,7 +103,7 @@ fun SubtitleStylePage(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
)
|
)
|
||||||
if (!focusedOnMargin) {
|
if (!focusedOnMargin && !focusedOnImageOpacity) {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.SpaceBetween,
|
verticalArrangement = Arrangement.SpaceBetween,
|
||||||
modifier =
|
modifier =
|
||||||
|
|
@ -109,7 +140,7 @@ fun SubtitleStylePage(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (focusedOnMargin) {
|
||||||
// Margin
|
// Margin
|
||||||
AndroidView(
|
AndroidView(
|
||||||
factory = { context ->
|
factory = { context ->
|
||||||
|
|
@ -129,17 +160,79 @@ fun SubtitleStylePage(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
)
|
)
|
||||||
|
} else if (focusedOnImageOpacity) {
|
||||||
|
AndroidView(
|
||||||
|
factory = { context ->
|
||||||
|
SubtitleView(context)
|
||||||
|
},
|
||||||
|
update = {
|
||||||
|
it.setStyle(
|
||||||
|
SubtitlePreferences
|
||||||
|
.newBuilder()
|
||||||
|
.apply {
|
||||||
|
resetSubtitles()
|
||||||
|
}.build()
|
||||||
|
.toSubtitleStyle(),
|
||||||
|
)
|
||||||
|
it.setCues(
|
||||||
|
listOf(
|
||||||
|
Cue
|
||||||
|
.Builder()
|
||||||
|
.setText("ExoPlayer only:\nImage based subtitles can be dimmed.")
|
||||||
|
.build(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.alpha(prefs.imageSubtitleOpacity / 100f),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PreferencesContent(
|
val display = LocalView.current.display
|
||||||
initialPreferences = preferences,
|
val prefList =
|
||||||
preferenceScreenOption = PreferenceScreenOption.SUBTITLES,
|
remember(hdrSettings, display) {
|
||||||
|
if (!hdrSettings && SubtitleSettings.shouldShowHdr(display)) {
|
||||||
|
// If not on HDR page and display is HDR capable, then show the HDR button
|
||||||
|
SubtitleSettings.preferences + SubtitleSettings.hdrPreferenceGroup
|
||||||
|
} else {
|
||||||
|
SubtitleSettings.preferences
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SubtitlePreferencesContent(
|
||||||
|
title =
|
||||||
|
if (hdrSettings) {
|
||||||
|
stringResource(R.string.hdr_subtitle_style)
|
||||||
|
} else {
|
||||||
|
stringResource(R.string.subtitle_style)
|
||||||
|
},
|
||||||
|
preferences =
|
||||||
|
if (hdrSettings) {
|
||||||
|
preferences.interfacePreferences.hdrSubtitlesPreferences
|
||||||
|
} else {
|
||||||
|
preferences.interfacePreferences.subtitlesPreferences
|
||||||
|
},
|
||||||
|
prefList = prefList,
|
||||||
|
onPreferenceChange = { newSubtitlePrefs ->
|
||||||
|
viewModel.preferenceDataStore.updateData {
|
||||||
|
it.updateInterfacePreferences {
|
||||||
|
if (hdrSettings) {
|
||||||
|
hdrSubtitlesPreferences = newSubtitlePrefs
|
||||||
|
} else {
|
||||||
|
subtitlesPreferences = newSubtitlePrefs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
onFocus = { groupIndex, prefIndex ->
|
onFocus = { groupIndex, prefIndex ->
|
||||||
|
val focusedPref =
|
||||||
focusedOnMargin =
|
SubtitleSettings.preferences
|
||||||
SubtitleSettings.preferences.getOrNull(groupIndex)?.preferences?.getOrNull(
|
.getOrNull(groupIndex)
|
||||||
prefIndex,
|
?.preferences
|
||||||
) == SubtitleSettings.Margin
|
?.getOrNull(prefIndex)
|
||||||
|
focusedOnMargin = focusedPref == SubtitleSettings.Margin
|
||||||
|
focusedOnImageOpacity = focusedPref == SubtitleSettings.ImageOpacity
|
||||||
},
|
},
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,7 @@ message SubtitlePreferences{
|
||||||
bool font_italic = 10;
|
bool font_italic = 10;
|
||||||
int32 margin = 11;
|
int32 margin = 11;
|
||||||
int32 edge_thickness = 12;
|
int32 edge_thickness = 12;
|
||||||
|
int32 image_subtitle_opacity = 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LiveTvPreferences {
|
message LiveTvPreferences {
|
||||||
|
|
@ -152,6 +153,7 @@ message InterfacePreferences {
|
||||||
SubtitlePreferences subtitles_preferences = 7;
|
SubtitlePreferences subtitles_preferences = 7;
|
||||||
LiveTvPreferences live_tv_preferences = 8;
|
LiveTvPreferences live_tv_preferences = 8;
|
||||||
BackdropStyle backdrop_style = 9;
|
BackdropStyle backdrop_style = 9;
|
||||||
|
SubtitlePreferences hdr_subtitles_preferences = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AdvancedPreferences {
|
message AdvancedPreferences {
|
||||||
|
|
|
||||||
|
|
@ -462,6 +462,8 @@
|
||||||
<string name="request_4k">Request in 4K</string>
|
<string name="request_4k">Request in 4K</string>
|
||||||
<string name="software_decoding_av1">AV1 software decoding</string>
|
<string name="software_decoding_av1">AV1 software decoding</string>
|
||||||
<string name="upgrade_mpv_toast">MPV is now the default player except for HDR.\nYou can change this in settings.</string>
|
<string name="upgrade_mpv_toast">MPV is now the default player except for HDR.\nYou can change this in settings.</string>
|
||||||
|
<string name="hdr_subtitle_style">HDR subtitle style</string>
|
||||||
|
<string name="image_subtitle_opacity">Image subtitle opacity</string>
|
||||||
|
|
||||||
<string-array name="theme_song_volume">
|
<string-array name="theme_song_volume">
|
||||||
<item>Disabled</item>
|
<item>Disabled</item>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue