Better shuffle & repeat buttons

This commit is contained in:
Damontecres 2026-03-02 17:30:39 -05:00
parent ff0417b7e7
commit 5a4528541a
No known key found for this signature in database

View file

@ -1,14 +1,29 @@
package com.github.damontecres.wholphin.ui.detail.music package com.github.damontecres.wholphin.ui.detail.music
import androidx.annotation.OptIn import androidx.annotation.OptIn
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.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.ui.compose.state.rememberNextButtonState import androidx.media3.ui.compose.state.rememberNextButtonState
@ -16,13 +31,20 @@ import androidx.media3.ui.compose.state.rememberPlayPauseButtonState
import androidx.media3.ui.compose.state.rememberPreviousButtonState import androidx.media3.ui.compose.state.rememberPreviousButtonState
import androidx.media3.ui.compose.state.rememberRepeatButtonState import androidx.media3.ui.compose.state.rememberRepeatButtonState
import androidx.media3.ui.compose.state.rememberShuffleButtonState import androidx.media3.ui.compose.state.rememberShuffleButtonState
import androidx.tv.material3.ClickableSurfaceDefaults
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.ui.AppColors
import com.github.damontecres.wholphin.ui.FontAwesome
import com.github.damontecres.wholphin.ui.PreviewTvSpec
import com.github.damontecres.wholphin.ui.components.Button
import com.github.damontecres.wholphin.ui.playback.ControllerViewState import com.github.damontecres.wholphin.ui.playback.ControllerViewState
import com.github.damontecres.wholphin.ui.playback.PlaybackAction import com.github.damontecres.wholphin.ui.playback.PlaybackAction
import com.github.damontecres.wholphin.ui.playback.PlaybackButtons import com.github.damontecres.wholphin.ui.playback.PlaybackButtons
import com.github.damontecres.wholphin.ui.playback.PlaybackFaButton
import com.github.damontecres.wholphin.ui.playback.buttonSpacing import com.github.damontecres.wholphin.ui.playback.buttonSpacing
import com.github.damontecres.wholphin.ui.theme.PreviewInteractionSource
import com.github.damontecres.wholphin.ui.theme.WholphinTheme
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
@ -74,35 +96,190 @@ fun NowPlayingButtons(
horizontalArrangement = Arrangement.spacedBy(buttonSpacing), horizontalArrangement = Arrangement.spacedBy(buttonSpacing),
modifier = Modifier.align(Alignment.CenterEnd), modifier = Modifier.align(Alignment.CenterEnd),
) { ) {
PlaybackFaButton( ShuffleButton(
iconRes = R.string.fa_shuffle, active = shuffleState.shuffleOn,
enabled = shuffleState.isEnabled,
onClick = { onClick = {
shuffleState.onClick() shuffleState.onClick()
}, },
onControllerInteraction = { controllerViewState.pulseControls() }, onControllerInteraction = { controllerViewState.pulseControls() },
textColor =
if (shuffleState.shuffleOn) {
MaterialTheme.colorScheme.secondary
} else {
Color.Unspecified
},
) )
PlaybackFaButton( RepeatButton(
iconRes = R.string.fa_repeat, repeatMode = repeatState.repeatModeState,
enabled = repeatState.isEnabled,
onClick = { onClick = {
repeatState.onClick() repeatState.onClick()
}, },
onControllerInteraction = { controllerViewState.pulseControls() }, onControllerInteraction = { controllerViewState.pulseControls() },
textColor =
when (repeatState.repeatModeState) {
Player.REPEAT_MODE_ALL -> MaterialTheme.colorScheme.secondary
// TODO
Player.REPEAT_MODE_ONE -> MaterialTheme.colorScheme.tertiary
else -> Color.Unspecified
},
) )
} }
} }
} }
@Composable
fun ShuffleButton(
active: Boolean,
onClick: () -> Unit,
onControllerInteraction: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
val selectedColor = MaterialTheme.colorScheme.border
val focused by interactionSource.collectIsFocusedAsState()
Button(
enabled = enabled,
onClick = onClick,
// shape = ButtonDefaults.shape(CircleShape),
colors =
ClickableSurfaceDefaults.colors(
containerColor = AppColors.TransparentBlack25,
focusedContainerColor = selectedColor,
),
contentPadding = PaddingValues(4.dp),
interactionSource = interactionSource,
modifier =
modifier
.size(36.dp, 36.dp)
.onFocusChanged { onControllerInteraction.invoke() },
) {
Text(
text = stringResource(R.string.fa_shuffle),
fontSize = 18.sp,
fontFamily = FontAwesome,
textAlign = TextAlign.Center,
color =
when {
focused && active -> MaterialTheme.colorScheme.onSurface
focused && !active -> MaterialTheme.colorScheme.surface
!focused && active -> MaterialTheme.colorScheme.onSurface
else -> MaterialTheme.colorScheme.onSurface.copy(alpha = .5f)
},
modifier =
Modifier
.fillMaxWidth()
.align(Alignment.CenterVertically),
)
}
}
@Composable
fun RepeatButton(
repeatMode: Int,
onClick: () -> Unit,
onControllerInteraction: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
val selectedColor = MaterialTheme.colorScheme.border
val focused by interactionSource.collectIsFocusedAsState()
Button(
enabled = enabled,
onClick = onClick,
// shape = ButtonDefaults.shape(CircleShape),
colors =
ClickableSurfaceDefaults.colors(
containerColor = AppColors.TransparentBlack25,
focusedContainerColor = selectedColor,
),
contentPadding = PaddingValues(4.dp),
interactionSource = interactionSource,
modifier =
modifier
.size(36.dp, 36.dp)
.onFocusChanged { onControllerInteraction.invoke() },
) {
Box(
Modifier
.fillMaxWidth()
.align(Alignment.CenterVertically),
) {
Text(
text = stringResource(R.string.fa_repeat),
fontSize = 18.sp,
fontFamily = FontAwesome,
textAlign = TextAlign.Center,
color =
if (focused) {
when (repeatMode) {
Player.REPEAT_MODE_ALL -> MaterialTheme.colorScheme.onSurface
Player.REPEAT_MODE_ONE -> MaterialTheme.colorScheme.onSurface
else -> MaterialTheme.colorScheme.surface
}
} else {
when (repeatMode) {
Player.REPEAT_MODE_ALL -> MaterialTheme.colorScheme.onSurface
Player.REPEAT_MODE_ONE -> MaterialTheme.colorScheme.onSurface
else -> MaterialTheme.colorScheme.onSurface.copy(alpha = .5f)
}
},
modifier =
Modifier
.fillMaxWidth()
.align(Alignment.Center),
)
if (repeatMode == Player.REPEAT_MODE_ONE) {
Text(
text = "1",
fontSize = 10.sp,
color = MaterialTheme.colorScheme.surface,
modifier =
Modifier
.offset(x = 4.dp)
.background(
color = MaterialTheme.colorScheme.onSurface,
shape = RoundedCornerShape(1.dp),
).align(Alignment.BottomStart),
)
}
}
}
}
@PreviewTvSpec
@Composable
fun ShuffleButtonPreview() {
val source = remember { PreviewInteractionSource() }
WholphinTheme {
Column {
Row {
RepeatButton(
repeatMode = Player.REPEAT_MODE_OFF,
onClick = {},
onControllerInteraction = {},
)
RepeatButton(
repeatMode = Player.REPEAT_MODE_ALL,
onClick = {},
onControllerInteraction = {},
)
RepeatButton(
repeatMode = Player.REPEAT_MODE_ONE,
onClick = {},
onControllerInteraction = {},
)
}
Row {
RepeatButton(
repeatMode = Player.REPEAT_MODE_OFF,
onClick = {},
onControllerInteraction = {},
interactionSource = source,
)
RepeatButton(
repeatMode = Player.REPEAT_MODE_ALL,
onClick = {},
onControllerInteraction = {},
interactionSource = source,
)
RepeatButton(
repeatMode = Player.REPEAT_MODE_ONE,
onClick = {},
onControllerInteraction = {},
interactionSource = source,
)
}
}
}
}