From 95859be3c64c8c78e13cd578e9a1f40e8e18159c Mon Sep 17 00:00:00 2001 From: Ray <154766448+damontecres@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:49:54 -0400 Subject: [PATCH] Add a small glow effect to the segment skip button (#1228) ## Description Adds a small glow to the border of the segment skip buttons, ie "Skip Intro", etc. Since this button appears as focused immediately, there's no contrast and it can be missed sometimes. So this glowing border draws the eye a bit better. Also a small change to reduce the minimum skip forward amount to 5 seconds . ### Related issues Skip forward: #1203 ### Testing Emulator. But this needs some "real world" testing on a TV, too, so this may need tweaking ## Screenshots https://github.com/user-attachments/assets/5488d468-aee4-4448-a823-6326ccb003da ## AI or LLM usage None --- .../wholphin/preferences/AppPreference.kt | 2 +- .../wholphin/ui/playback/PlaybackPage.kt | 6 +- .../wholphin/ui/playback/SkipSegmentButton.kt | 92 +++++++++++++++++++ 3 files changed, 95 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/com/github/damontecres/wholphin/ui/playback/SkipSegmentButton.kt 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 26e8e623..253d8c6d 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 @@ -60,7 +60,7 @@ sealed interface AppPreference { AppSliderPreference( title = R.string.skip_forward_preference, defaultValue = 30, - min = 10, + min = 5, max = 5.minutes.inWholeSeconds, interval = 5, getter = { 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 3159c4dd..8fb08653 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 @@ -68,14 +68,12 @@ import com.github.damontecres.wholphin.ui.AspectRatios import com.github.damontecres.wholphin.ui.LocalImageUrlService import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.LoadingPage -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.preferences.subtitle.SubtitleSettings.applyToMpv 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.seasonEpisode -import com.github.damontecres.wholphin.ui.skipStringRes import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.util.ExceptionHandler import com.github.damontecres.wholphin.util.LoadingState @@ -463,8 +461,8 @@ fun PlaybackPageContent( delay(10.seconds) viewModel.updateSegment(segment.segment.id, true) } - TextButton( - stringRes = segment.segment.type.skipStringRes, + SkipSegmentButton( + type = segment.segment.type, onClick = { viewModel.updateSegment(segment.segment.id, false) }, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/playback/SkipSegmentButton.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/SkipSegmentButton.kt new file mode 100644 index 00000000..4270bf48 --- /dev/null +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/playback/SkipSegmentButton.kt @@ -0,0 +1,92 @@ +package com.github.damontecres.wholphin.ui.playback + +import androidx.compose.animation.animateColor +import androidx.compose.animation.core.InfiniteRepeatableSpec +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.tv.material3.MaterialTheme +import androidx.tv.material3.Text +import com.github.damontecres.wholphin.ui.PreviewTvSpec +import com.github.damontecres.wholphin.ui.components.Button +import com.github.damontecres.wholphin.ui.skipStringRes +import com.github.damontecres.wholphin.ui.theme.PreviewInteractionSource +import com.github.damontecres.wholphin.ui.theme.WholphinTheme +import org.jellyfin.sdk.model.api.MediaSegmentType + +@Composable +fun SkipSegmentButton( + type: MediaSegmentType, + onClick: () -> Unit, + modifier: Modifier = Modifier, + onLongClick: (() -> Unit)? = null, + interactionSource: MutableInteractionSource? = null, +) { + val infiniteTransition = rememberInfiniteTransition("SkipSegmentButton") + val color by infiniteTransition.animateColor( + initialValue = MaterialTheme.colorScheme.onSurface, + targetValue = Color.Transparent, + animationSpec = + InfiniteRepeatableSpec( + animation = tween(durationMillis = 1500, easing = LinearEasing), + repeatMode = RepeatMode.Reverse, + ), + label = "SkipSegmentButton_color", + ) + Button( + onClick = onClick, + modifier = + modifier.drawBehind { + val px = 6.dp.toPx() + drawRoundRect( + color = color, + cornerRadius = CornerRadius(this.size.width * .5f), + topLeft = Offset(-px, -px), + size = Size(this.size.width + px * 2, this.size.height + px * 2), + ) + }, + onLongClick = onLongClick, + enabled = true, + contentPadding = + PaddingValues( + start = 8.dp, + top = 4.dp, + end = 8.dp, + bottom = 4.dp, + ), + contentHeight = 32.dp, + interactionSource = interactionSource, + content = { + Text(text = stringResource(type.skipStringRes)) + }, + ) +} + +@PreviewTvSpec +@Composable +fun SkipSegmentButtonPreview() { + WholphinTheme { + val source = remember { PreviewInteractionSource() } + SkipSegmentButton( + type = MediaSegmentType.INTRO, + onClick = {}, + modifier = Modifier.padding(16.dp), + interactionSource = source, + ) + } +}