diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt index 97bc7cdf..9def0960 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt @@ -668,6 +668,12 @@ sealed interface AppPreference { summaryOff = R.string.disabled, ) + val SubtitleStyle = + AppDestinationPreference( + title = R.string.subtitle_style, + destination = Destination.Settings(PreferenceScreenOption.SUBTITLES), + ) + val PlayerBackendPref = AppChoicePreference( title = R.string.player_backend, @@ -704,6 +710,7 @@ val basicPreferences = AppPreference.RewatchNextUp, AppPreference.PlayThemeMusic, AppPreference.RememberSelectedTab, + AppPreference.SubtitleStyle, AppPreference.ThemeColors, ), ), diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt index f5f423c1..a4c38f66 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreferencesSerializer.kt @@ -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 +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/SliderBar.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/SliderBar.kt index 2b57a73c..a0ba6ae3 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/SliderBar.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/SliderBar.kt @@ -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( diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackOverlay.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackOverlay.kt index b96c613a..240b3b26 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackOverlay.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackOverlay.kt @@ -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, + ) } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt index 2a0b691d..ddec061d 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/PlaybackPage.kt @@ -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 = { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/SeekPreviewImage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/SeekPreviewImage.kt index 6f59d455..1f060b15 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/SeekPreviewImage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/SeekPreviewImage.kt @@ -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, ) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferenceUtils.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferenceUtils.kt index a88e3a4b..71dd09fc 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferenceUtils.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferenceUtils.kt @@ -35,6 +35,7 @@ enum class PreferenceScreenOption { BASIC, ADVANCED, USER_INTERFACE, + SUBTITLES, ; companion object { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt index 92b5f88b..41d498cf 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt @@ -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, + ) + } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesViewModel.kt index 047b5c85..e73e0cef 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesViewModel.kt @@ -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.updateData { + it.updateSubtitlePreferences { + resetSubtitles() + } + } + } + } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitleSettings.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitleSettings.kt new file mode 100644 index 00000000..e3faf42d --- /dev/null +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitleSettings.kt @@ -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( + 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( + 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( + 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( + 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( + 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 + }, + ) + } +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitleStylePage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitleStylePage.kt new file mode 100644 index 00000000..6a33710c --- /dev/null +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/subtitle/SubtitleStylePage.kt @@ -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), + ) + } +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/util/AppUpgradeHandler.kt b/app/src/main/java/com/github/damontecres/wholphin/util/AppUpgradeHandler.kt index cd51e07a..87074f7e 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/util/AppUpgradeHandler.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/util/AppUpgradeHandler.kt @@ -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) + } } diff --git a/app/src/main/proto/WholphinDataStore.proto b/app/src/main/proto/WholphinDataStore.proto index f08a9ff2..e25d421b 100644 --- a/app/src/main/proto/WholphinDataStore.proto +++ b/app/src/main/proto/WholphinDataStore.proto @@ -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 { diff --git a/app/src/main/res/mipmap-hdpi/eclipse.jpeg b/app/src/main/res/mipmap-hdpi/eclipse.jpeg new file mode 100644 index 00000000..ea40649d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/eclipse.jpeg differ diff --git a/app/src/main/res/values/preferences.xml b/app/src/main/res/values/preferences.xml index be288e01..f57fdc6c 100644 --- a/app/src/main/res/values/preferences.xml +++ b/app/src/main/res/values/preferences.xml @@ -47,6 +47,23 @@ Update available URL used to check for app updates Update URL + Font size + Font color + Font opacity + Edge style + Edge color + Background opacity + Background style + Subtitle style + Background color + Reset + Bold font + + Playback Backend + MPV: Use hardware decoding + Disable if you experience crashes + MPV Options + ExoPlayer Options Disabled @@ -90,9 +107,33 @@ During end credits/outro + + White + Black + Light Gray + Dark Gray + Red + Yellow + Green + Cyan + Blue + Magenta + + + + None + Outline + Shadow + + + + None + Wrap + Boxed + + ExoPlayer (default) MPV (Experimental) - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 47d4537d..7cefe044 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -144,11 +144,9 @@ Add to playlist Pause with one click Press D-Pad center to pause/play - Playback Backend - MPV: Use hardware decoding - Disable if you experience crashes - MPV Options - ExoPlayer Options + Italicize font + Font + Background %s downloads