Merge branch 'main' into develop/mpv

This commit is contained in:
Damontecres 2025-11-08 19:57:44 -05:00
commit 6d585a2a99
No known key found for this signature in database
16 changed files with 612 additions and 97 deletions

View file

@ -668,6 +668,12 @@ sealed interface AppPreference<T> {
summaryOff = R.string.disabled,
)
val SubtitleStyle =
AppDestinationPreference(
title = R.string.subtitle_style,
destination = Destination.Settings(PreferenceScreenOption.SUBTITLES),
)
val PlayerBackendPref =
AppChoicePreference<PlayerBackend>(
title = R.string.player_backend,
@ -704,6 +710,7 @@ val basicPreferences =
AppPreference.RewatchNextUp,
AppPreference.PlayThemeMusic,
AppPreference.RememberSelectedTab,
AppPreference.SubtitleStyle,
AppPreference.ThemeColors,
),
),

View file

@ -1,7 +1,9 @@
package com.github.damontecres.wholphin.preferences
import androidx.compose.ui.graphics.toArgb
import androidx.datastore.core.CorruptionException
import androidx.datastore.core.Serializer
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings
import com.google.protobuf.InvalidProtocolBufferException
import java.io.InputStream
import java.io.OutputStream
@ -83,6 +85,13 @@ class AppPreferencesSerializer
navDrawerSwitchOnFocus =
AppPreference.NavDrawerSwitchOnFocus.defaultValue
showClock = AppPreference.ShowClock.defaultValue
subtitlesPreferences =
SubtitlePreferences
.newBuilder()
.apply {
resetSubtitles()
}.build()
}.build()
}.build()
@ -126,3 +135,21 @@ inline fun AppPreferences.updateInterfacePreferences(block: InterfacePreferences
update {
interfacePreferences = interfacePreferences.toBuilder().apply(block).build()
}
inline fun AppPreferences.updateSubtitlePreferences(block: SubtitlePreferences.Builder.() -> Unit): AppPreferences =
updateInterfacePreferences {
subtitlesPreferences = subtitlesPreferences.toBuilder().apply(block).build()
}
fun SubtitlePreferences.Builder.resetSubtitles() {
fontSize = SubtitleSettings.FontSize.defaultValue.toInt()
fontColor = SubtitleSettings.FontColor.defaultValue.toArgb()
fontBold = SubtitleSettings.FontBold.defaultValue
fontItalic = SubtitleSettings.FontItalic.defaultValue
fontOpacity = SubtitleSettings.FontOpacity.defaultValue.toInt()
edgeColor = SubtitleSettings.EdgeColor.defaultValue.toArgb()
edgeStyle = SubtitleSettings.EdgeStylePref.defaultValue
backgroundColor = SubtitleSettings.BackgroundColor.defaultValue.toArgb()
backgroundOpacity = SubtitleSettings.BackgroundOpacity.defaultValue.toInt()
backgroundStyle = SubtitleSettings.BackgroundStylePref.defaultValue
}

View file

@ -44,7 +44,7 @@ fun SliderBar(
targetValue = 6.dp.times((if (isFocused) 2f else 1f)),
)
var currentValue by remember(value) { mutableLongStateOf(value) }
val percent = currentValue.toFloat() / (max - min)
val percent = (currentValue - min).toFloat() / (max - min)
val handleSeekEventModifier =
Modifier.handleDPadKeyEvents(

View file

@ -342,37 +342,50 @@ fun PlaybackOverlay(
(playerControls.currentPosition.toFloat() / playerControls.duration)
}
}
if (trickplayInfo != null) {
AnimatedVisibility(
seekProgressPercent >= 0 && seekBarFocused,
) {
val tilesPerImage = trickplayInfo.tileWidth * trickplayInfo.tileHeight
val index =
(seekProgressMs / trickplayInfo.interval).toInt() / tilesPerImage
val imageUrl = trickplayUrlFor(index)
if (imageUrl != null) {
Box(
modifier =
Modifier
.align(Alignment.Center)
.fillMaxWidth(.95f),
) {
SeekPreviewImage(
modifier =
Modifier
.align(Alignment.BottomStart)
.offsetByPercent(
xPercentage = seekProgressPercent.coerceIn(0f, 1f),
).padding(bottom = controllerHeight - titleHeight - subtitleHeight),
previewImageUrl = imageUrl,
duration = playerControls.duration,
seekProgressMs = seekProgressMs,
videoWidth = trickplayInfo.width,
videoHeight = trickplayInfo.height,
trickPlayInfo = trickplayInfo,
)
AnimatedVisibility(
seekProgressPercent >= 0 && seekBarFocused,
) {
Box(
modifier =
Modifier
.align(Alignment.Center)
.fillMaxWidth(.95f),
) {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier =
Modifier
.align(Alignment.BottomStart)
.offsetByPercent(
xPercentage = seekProgressPercent.coerceIn(0f, 1f),
).padding(bottom = controllerHeight - titleHeight - subtitleHeight),
) {
if (trickplayInfo != null) {
val tilesPerImage = trickplayInfo.tileWidth * trickplayInfo.tileHeight
val index =
(seekProgressMs / trickplayInfo.interval).toInt() / tilesPerImage
val imageUrl = trickplayUrlFor(index)
if (imageUrl != null) {
SeekPreviewImage(
modifier =
Modifier,
previewImageUrl = imageUrl,
duration = playerControls.duration,
seekProgressMs = seekProgressMs,
videoWidth = trickplayInfo.width,
videoHeight = trickplayInfo.height,
trickPlayInfo = trickplayInfo,
)
}
}
Text(
text = (seekProgressMs / 1000L).seconds.toString(),
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.labelLarge,
)
}
}
}

View file

@ -1,6 +1,7 @@
package com.github.damontecres.wholphin.ui.playback
import androidx.activity.compose.BackHandler
import androidx.annotation.Dimension
import androidx.annotation.OptIn
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
@ -70,6 +71,7 @@ import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings.toSubtitleStyle
import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.ExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState
@ -285,8 +287,10 @@ fun PlaybackPage(
AndroidView(
factory = { context ->
SubtitleView(context).apply {
setUserDefaultStyle()
setUserDefaultTextSize()
preferences.appPreferences.interfacePreferences.subtitlesPreferences.let {
setStyle(it.toSubtitleStyle())
setFixedTextSize(Dimension.SP, it.fontSize.toFloat())
}
}
},
update = {

View file

@ -2,12 +2,9 @@ package com.github.damontecres.wholphin.ui.playback
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
@ -17,7 +14,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import coil3.compose.AsyncImage
import coil3.request.CachePolicy
import coil3.request.ImageRequest
@ -25,7 +21,6 @@ import coil3.request.transformations
import com.github.damontecres.wholphin.ui.CoilTrickplayTransformation
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import org.jellyfin.sdk.model.api.TrickplayInfo
import kotlin.time.Duration.Companion.seconds
fun Modifier.offsetByPercent(
xPercentage: Float,
@ -85,55 +80,44 @@ fun SeekPreviewImage(
) {
val context = LocalContext.current
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
if (previewImageUrl.isNotNullOrBlank() &&
videoWidth != null &&
videoHeight != null
) {
if (previewImageUrl.isNotNullOrBlank() &&
videoWidth != null &&
videoHeight != null
) {
val height = 160.dp
val width = height * (videoWidth.toFloat() / videoHeight)
val heightPx = with(LocalDensity.current) { height.toPx().toInt() }
val widthPx = with(LocalDensity.current) { width.toPx().toInt() }
val height = 160.dp
val width = height * (videoWidth.toFloat() / videoHeight)
val heightPx = with(LocalDensity.current) { height.toPx().toInt() }
val widthPx = with(LocalDensity.current) { width.toPx().toInt() }
val index = (seekProgressMs.toDouble() / trickPlayInfo.interval).toInt() // Which tile
val numberOfTitlesPerImage = trickPlayInfo.tileHeight * trickPlayInfo.tileWidth
val imageIndex = index % numberOfTitlesPerImage
val index = (seekProgressMs.toDouble() / trickPlayInfo.interval).toInt() // Which tile
val numberOfTitlesPerImage = trickPlayInfo.tileHeight * trickPlayInfo.tileWidth
val imageIndex = index % numberOfTitlesPerImage
AsyncImage(
modifier =
Modifier
.width(width)
.height(height)
.background(Color.Black)
.border(1.5.dp, color = MaterialTheme.colorScheme.border),
model =
ImageRequest
.Builder(context)
.data(previewImageUrl)
.memoryCachePolicy(CachePolicy.ENABLED)
.transformations(
CoilTrickplayTransformation(
widthPx,
heightPx,
trickPlayInfo.tileHeight,
trickPlayInfo.tileWidth,
imageIndex,
index,
),
).build(),
contentScale = ContentScale.None,
contentDescription = null,
placeholder = placeHolder,
)
}
Text(
text = (seekProgressMs / 1000L).seconds.toString(),
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.labelLarge,
AsyncImage(
modifier =
modifier
.width(width)
.height(height)
.background(Color.Black)
.border(1.5.dp, color = MaterialTheme.colorScheme.border),
model =
ImageRequest
.Builder(context)
.data(previewImageUrl)
.memoryCachePolicy(CachePolicy.ENABLED)
.transformations(
CoilTrickplayTransformation(
widthPx,
heightPx,
trickPlayInfo.tileHeight,
trickPlayInfo.tileWidth,
imageIndex,
index,
),
).build(),
contentScale = ContentScale.None,
contentDescription = null,
placeholder = placeHolder,
)
}
}

View file

@ -35,6 +35,7 @@ enum class PreferenceScreenOption {
BASIC,
ADVANCED,
USER_INTERFACE,
SUBTITLES,
;
companion object {

View file

@ -50,6 +50,8 @@ import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.playOnClickSound
import com.github.damontecres.wholphin.ui.playSoundOnFocus
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.tryRequestFocus
import com.github.damontecres.wholphin.util.ExceptionHandler
@ -94,12 +96,14 @@ fun PreferencesContent(
PreferenceScreenOption.BASIC -> basicPreferences
PreferenceScreenOption.ADVANCED -> advancedPreferences
PreferenceScreenOption.USER_INTERFACE -> uiPreferences
PreferenceScreenOption.SUBTITLES -> SubtitleSettings.preferences
}
val screenTitle =
when (preferenceScreenOption) {
PreferenceScreenOption.BASIC -> R.string.settings
PreferenceScreenOption.ADVANCED -> R.string.advanced_settings
PreferenceScreenOption.USER_INTERFACE -> R.string.ui_interface
PreferenceScreenOption.SUBTITLES -> R.string.subtitle_style
}
var visible by remember { mutableStateOf(false) }
@ -302,6 +306,19 @@ fun PreferencesContent(
)
}
SubtitleSettings.Reset -> {
ClickPreference(
title = stringResource(pref.title),
onClick = {
viewModel.resetSubtitleSettings()
},
modifier = Modifier,
summary = pref.summary(context, null),
onLongClick = {},
interactionSource = interactionSource,
)
}
else -> {
val value = pref.getter.invoke(preferences)
ComposablePreference(
@ -357,13 +374,24 @@ fun PreferencesPage(
Box(
modifier = modifier.background(MaterialTheme.colorScheme.background),
) {
PreferencesContent(
initialPreferences,
preferenceScreenOption,
Modifier
.fillMaxWidth(.4f)
.fillMaxHeight()
.align(Alignment.TopEnd),
)
when (preferenceScreenOption) {
PreferenceScreenOption.BASIC,
PreferenceScreenOption.ADVANCED,
PreferenceScreenOption.USER_INTERFACE,
->
PreferencesContent(
initialPreferences,
preferenceScreenOption,
Modifier
.fillMaxWidth(.4f)
.fillMaxHeight()
.align(Alignment.TopEnd),
)
PreferenceScreenOption.SUBTITLES ->
SubtitleStylePage(
initialPreferences,
)
}
}
}

View file

@ -12,6 +12,8 @@ import com.github.damontecres.wholphin.data.isPinned
import com.github.damontecres.wholphin.data.model.NavDrawerPinnedItem
import com.github.damontecres.wholphin.data.model.NavPinType
import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.resetSubtitles
import com.github.damontecres.wholphin.preferences.updateSubtitlePreferences
import com.github.damontecres.wholphin.ui.detail.DebugViewModel.Companion.sendAppLogs
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
@ -91,4 +93,20 @@ class PreferencesViewModel
fun sendAppLogs() {
sendAppLogs(context, api, clientInfo, deviceInfo)
}
fun resetSubtitleSettings() {
viewModelScope.launchIO {
resetSubtitleSettings(preferenceDataStore)
}
}
companion object {
suspend fun resetSubtitleSettings(appPreferences: DataStore<AppPreferences>) {
appPreferences.updateData {
it.updateSubtitlePreferences {
resetSubtitles()
}
}
}
}
}

View file

@ -0,0 +1,248 @@
package com.github.damontecres.wholphin.ui.preferences.subtitle
import android.graphics.Typeface
import androidx.annotation.OptIn
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.media3.common.util.UnstableApi
import androidx.media3.ui.CaptionStyleCompat
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.preferences.AppChoicePreference
import com.github.damontecres.wholphin.preferences.AppClickablePreference
import com.github.damontecres.wholphin.preferences.AppSliderPreference
import com.github.damontecres.wholphin.preferences.AppSwitchPreference
import com.github.damontecres.wholphin.preferences.BackgroundStyle
import com.github.damontecres.wholphin.preferences.EdgeStyle
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.preferences.PreferenceGroup
object SubtitleSettings {
val FontSize =
AppSliderPreference(
title = R.string.font_size,
defaultValue = 24,
min = 8,
max = 70,
interval = 2,
getter = {
it.interfacePreferences.subtitlesPreferences.fontSize
.toLong()
},
setter = { prefs, value ->
prefs.updateSubtitlePreferences { fontSize = value.toInt() }
},
summarizer = { value -> value?.toString() },
)
private val colorList =
listOf(
Color.White,
Color.Black,
Color.LightGray,
Color.DarkGray,
Color.Red,
Color.Yellow,
Color.Green,
Color.Cyan,
Color.Blue,
Color.Magenta,
)
val FontColor =
AppChoicePreference<Color>(
title = R.string.font_color,
defaultValue = Color.White,
getter = { Color(it.interfacePreferences.subtitlesPreferences.fontColor) },
setter = { prefs, value ->
prefs.updateSubtitlePreferences { fontColor = value.toArgb().and(0x00FFFFFF) }
},
displayValues = R.array.font_colors,
indexToValue = { colorList.getOrNull(it) ?: Color.White },
valueToIndex = { value ->
val color = value.toArgb().and(0x00FFFFFF)
colorList.indexOfFirstOrNull { color == it.toArgb().and(0x00FFFFFF) } ?: 0
},
)
val FontBold =
AppSwitchPreference(
title = R.string.bold_font,
defaultValue = false,
getter = { it.interfacePreferences.subtitlesPreferences.fontBold },
setter = { prefs, value ->
prefs.updateSubtitlePreferences { fontBold = value }
},
)
val FontItalic =
AppSwitchPreference(
title = R.string.italic_font,
defaultValue = false,
getter = { it.interfacePreferences.subtitlesPreferences.fontItalic },
setter = { prefs, value ->
prefs.updateSubtitlePreferences { fontItalic = value }
},
)
val FontOpacity =
AppSliderPreference(
title = R.string.font_opacity,
defaultValue = 100,
min = 10,
max = 100,
interval = 10,
getter = {
it.interfacePreferences.subtitlesPreferences.fontOpacity
.toLong()
},
setter = { prefs, value ->
prefs.updateSubtitlePreferences { fontOpacity = value.toInt() }
},
summarizer = { value -> value?.let { "$it%" } },
)
val EdgeStylePref =
AppChoicePreference<EdgeStyle>(
title =
R.string.edge_style,
defaultValue = EdgeStyle.EDGE_SOLID,
getter = { it.interfacePreferences.subtitlesPreferences.edgeStyle },
setter = { prefs, value ->
prefs.updateSubtitlePreferences { edgeStyle = value }
},
displayValues = R.array.subtitle_edge,
indexToValue = { EdgeStyle.forNumber(it) },
valueToIndex = { it.number },
)
val EdgeColor =
AppChoicePreference<Color>(
title = R.string.edge_color,
defaultValue = Color.Black,
getter = { Color(it.interfacePreferences.subtitlesPreferences.edgeColor) },
setter = { prefs, value ->
prefs.updateSubtitlePreferences { edgeColor = value.toArgb().and(0x00FFFFFF) }
},
displayValues = R.array.font_colors,
indexToValue = { colorList.getOrNull(it) ?: Color.White },
valueToIndex = { value ->
val color = value.toArgb().and(0x00FFFFFF)
colorList.indexOfFirstOrNull { color == it.toArgb().and(0x00FFFFFF) } ?: 0
},
)
val BackgroundColor =
AppChoicePreference<Color>(
title = R.string.background_color,
defaultValue = Color.Transparent,
getter = { Color(it.interfacePreferences.subtitlesPreferences.backgroundColor) },
setter = { prefs, value ->
prefs.updateSubtitlePreferences { backgroundColor = value.toArgb().and(0x00FFFFFF) }
},
displayValues = R.array.font_colors,
indexToValue = { colorList.getOrNull(it) ?: Color.White },
valueToIndex = { value ->
val color = value.toArgb().and(0x00FFFFFF)
colorList.indexOfFirstOrNull { color == it.toArgb().and(0x00FFFFFF) } ?: 0
},
)
val BackgroundOpacity =
AppSliderPreference(
title = R.string.background_opacity,
defaultValue = 50,
min = 10,
max = 100,
interval = 10,
getter = {
it.interfacePreferences.subtitlesPreferences.backgroundOpacity
.toLong()
},
setter = { prefs, value ->
prefs.updateSubtitlePreferences { backgroundOpacity = value.toInt() }
},
summarizer = { value -> value?.let { "$it%" } },
)
val BackgroundStylePref =
AppChoicePreference<BackgroundStyle>(
title =
R.string.background_style,
defaultValue = BackgroundStyle.BG_NONE,
getter = { it.interfacePreferences.subtitlesPreferences.backgroundStyle },
setter = { prefs, value ->
prefs.updateSubtitlePreferences { backgroundStyle = value }
},
displayValues = R.array.background_style,
indexToValue = { BackgroundStyle.forNumber(it) },
valueToIndex = { it.number },
)
val Reset =
AppClickablePreference(
title = R.string.reset,
getter = { },
setter = { prefs, _ -> prefs },
)
val preferences =
listOf(
PreferenceGroup(
title = R.string.font,
preferences =
listOf(
FontSize,
FontColor,
FontBold,
FontItalic,
FontOpacity,
),
),
PreferenceGroup(
title = R.string.edge_style,
preferences =
listOf(
EdgeStylePref,
EdgeColor,
),
),
PreferenceGroup(
title = R.string.background,
preferences =
listOf(
BackgroundStylePref,
BackgroundColor,
BackgroundOpacity,
),
),
PreferenceGroup(
title = R.string.more,
preferences =
listOf(Reset),
),
)
@OptIn(UnstableApi::class)
fun SubtitlePreferences.toSubtitleStyle(): CaptionStyleCompat {
val fo = (fontOpacity / 100.0 * 255).toInt().shl(24)
val bg = (backgroundOpacity / 100.0 * 255).toInt().shl(24).or(backgroundColor)
return CaptionStyleCompat(
fo.or(fontColor),
if (backgroundStyle == BackgroundStyle.BG_WRAP)bg else 0,
if (backgroundStyle == BackgroundStyle.BG_BOXED) bg else 0,
when (edgeStyle) {
EdgeStyle.EDGE_NONE, EdgeStyle.UNRECOGNIZED -> CaptionStyleCompat.EDGE_TYPE_NONE
EdgeStyle.EDGE_SOLID -> CaptionStyleCompat.EDGE_TYPE_OUTLINE
EdgeStyle.EDGE_SHADOW -> CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW
},
fo.or(edgeColor),
when {
fontBold && fontItalic -> Typeface.defaultFromStyle(Typeface.BOLD_ITALIC)
fontBold -> Typeface.defaultFromStyle(Typeface.BOLD)
fontItalic -> Typeface.defaultFromStyle(Typeface.ITALIC)
else -> Typeface.DEFAULT
},
)
}
}

View file

@ -0,0 +1,116 @@
package com.github.damontecres.wholphin.ui.preferences.subtitle
import androidx.annotation.Dimension
import androidx.annotation.OptIn
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
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.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.media3.common.text.Cue
import androidx.media3.common.util.UnstableApi
import androidx.media3.ui.SubtitleView
import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
import com.github.damontecres.wholphin.ui.preferences.PreferencesContent
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
import com.github.damontecres.wholphin.ui.preferences.subtitle.SubtitleSettings.toSubtitleStyle
import timber.log.Timber
@OptIn(UnstableApi::class)
@Composable
fun SubtitleStylePage(
initialPreferences: AppPreferences,
modifier: Modifier = Modifier,
viewModel: PreferencesViewModel = hiltViewModel(),
) {
var preferences by remember { mutableStateOf(initialPreferences) }
LaunchedEffect(Unit) {
viewModel.preferenceDataStore.data.collect {
preferences = it
}
}
Timber.v("SubtitleStylePage")
val prefs = preferences.interfacePreferences.subtitlesPreferences
Row(
modifier = modifier,
) {
Box(
contentAlignment = Alignment.BottomCenter,
modifier =
Modifier
.fillMaxSize()
.weight(1f),
) {
Image(
painter = painterResource(R.mipmap.eclipse),
contentDescription = null,
contentScale = ContentScale.FillBounds,
modifier =
Modifier
.fillMaxSize(),
)
Column(
verticalArrangement = Arrangement.SpaceBetween,
modifier =
Modifier
.padding(16.dp)
.fillMaxSize(),
) {
val examples =
mapOf(
"Subtitles will look like this" to 48.dp,
"This is another example" to 24.dp,
"Longer multi line subtitles will\nlook like this" to 0.dp,
)
examples.forEach { (text, padding) ->
AndroidView(
factory = { context ->
SubtitleView(context)
},
update = {
it.setStyle(prefs.toSubtitleStyle())
it.setFixedTextSize(Dimension.SP, prefs.fontSize.toFloat())
it.setCues(
listOf(
Cue.Builder().setText(text).build(),
),
)
},
modifier =
Modifier
.weight(1f)
.fillMaxWidth()
.padding(bottom = padding),
)
}
}
}
PreferencesContent(
initialPreferences = preferences,
preferenceScreenOption = PreferenceScreenOption.SUBTITLES,
modifier =
Modifier
.fillMaxHeight()
.fillMaxWidth(.25f),
)
}
}

View file

@ -10,6 +10,7 @@ import com.github.damontecres.wholphin.preferences.AppPreference
import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.updateInterfacePreferences
import com.github.damontecres.wholphin.preferences.updatePlaybackOverrides
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import timber.log.Timber
import java.io.File
@ -115,4 +116,7 @@ suspend fun upgradeApp(
}
}
}
if (previous.isEqualOrBefore(Version.fromString("0.2.7-1-g0"))) {
PreferencesViewModel.resetSubtitleSettings(appPreferences)
}
}

View file

@ -94,6 +94,31 @@ enum AppThemeColors {
ORANGE = 3;
}
enum EdgeStyle{
EDGE_NONE = 0;
EDGE_SOLID = 1;
EDGE_SHADOW = 2;
}
enum BackgroundStyle{
BG_NONE = 0;
BG_WRAP = 1;
BG_BOXED = 2;
}
message SubtitlePreferences{
int32 font_size = 1;
int32 font_color = 2;
int32 font_opacity = 3;
int32 edge_color = 4;
EdgeStyle edge_style = 5;
int32 background_color = 6;
int32 background_opacity = 7;
BackgroundStyle background_style = 8;
bool font_bold = 9;
bool font_italic = 10;
}
message InterfacePreferences {
ThemeSongVolume play_theme_songs = 1;
bool remember_selected_tab = 2;
@ -101,6 +126,7 @@ message InterfacePreferences {
AppThemeColors app_theme_colors = 4;
bool nav_drawer_switch_on_focus = 5;
bool show_clock = 6;
SubtitlePreferences subtitles_preferences = 7;
}
message AppPreferences {

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

View file

@ -47,6 +47,23 @@
<string name="update_available">Update available</string>
<string name="update_url_summary">URL used to check for app updates</string>
<string name="update_url">Update URL</string>
<string name="font_size">Font size</string>
<string name="font_color">Font color</string>
<string name="font_opacity">Font opacity</string>
<string name="edge_style">Edge style</string>
<string name="edge_color">Edge color</string>
<string name="background_opacity">Background opacity</string>
<string name="background_style">Background style</string>
<string name="subtitle_style">Subtitle style</string>
<string name="background_color">Background color</string>
<string name="reset">Reset</string>
<string name="bold_font">Bold font</string>
<string name="player_backend">Playback Backend</string>
<string name="mpv_hardware_decoding">MPV: Use hardware decoding</string>
<string name="disable_if_crash">Disable if you experience crashes</string>
<string name="mpv_options">MPV Options</string>
<string name="exoplayer_options">ExoPlayer Options</string>
<string-array name="theme_song_volume">
<item>Disabled</item>
@ -90,9 +107,33 @@
<item>During end credits/outro</item>
</string-array>
<string-array name="font_colors">
<item>White</item>
<item>Black</item>
<item>Light Gray</item>
<item>Dark Gray</item>
<item>Red</item>
<item>Yellow</item>
<item>Green</item>
<item>Cyan</item>
<item>Blue</item>
<item>Magenta</item>
</string-array>
<string-array name="subtitle_edge">
<item>None</item>
<item>Outline</item>
<item>Shadow</item>
</string-array>
<string-array name="background_style">
<item>None</item>
<item>Wrap</item>
<item>Boxed</item>
</string-array>
<string-array name="player_backend_options">
<item>ExoPlayer (default)</item>
<item>MPV (Experimental)</item>
</string-array>
</resources>

View file

@ -144,11 +144,9 @@
<string name="add_to_playlist">Add to playlist</string>
<string name="one_click_pause">Pause with one click</string>
<string name="one_click_pause_summary_on">Press D-Pad center to pause/play</string>
<string name="player_backend">Playback Backend</string>
<string name="mpv_hardware_decoding">MPV: Use hardware decoding</string>
<string name="disable_if_crash">Disable if you experience crashes</string>
<string name="mpv_options">MPV Options</string>
<string name="exoplayer_options">ExoPlayer Options</string>
<string name="italic_font">Italicize font</string>
<string name="font">Font</string>
<string name="background">Background</string>
<plurals name="downloads">
<item quantity="zero">%s downloads</item>