feat(tv): change playback options layout to horizontal row

This commit is contained in:
Fredrik Burmester
2026-01-29 18:17:43 +01:00
parent bf3a37c61c
commit 53902aebab
2 changed files with 15 additions and 47 deletions

View File

@@ -184,10 +184,6 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
null, null,
); );
// State for last option button ref (used for upward focus guide from cast)
const [_lastOptionButtonRef, setLastOptionButtonRef] =
useState<View | null>(null);
// Get available audio tracks // Get available audio tracks
const audioTracks = useMemo(() => { const audioTracks = useMemo(() => {
const streams = selectedOptions?.mediaSource?.MediaStreams?.filter( const streams = selectedOptions?.mediaSource?.MediaStreams?.filter(
@@ -442,25 +438,6 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
return `${api.basePath}/Items/${item.SeriesId}/Images/Thumb?fillHeight=700&quality=80`; return `${api.basePath}/Items/${item.SeriesId}/Images/Thumb?fillHeight=700&quality=80`;
}, [api, item]); }, [api, item]);
// Determine which option button is the last one (for focus guide targeting)
const lastOptionButton = useMemo(() => {
const hasSubtitleOption =
subtitleStreams.length > 0 ||
selectedOptions?.subtitleIndex !== undefined;
const hasAudioOption = audioTracks.length > 0;
const hasMediaSourceOption = mediaSources.length > 1;
if (hasSubtitleOption) return "subtitle";
if (hasAudioOption) return "audio";
if (hasMediaSourceOption) return "mediaSource";
return "quality";
}, [
subtitleStreams.length,
selectedOptions?.subtitleIndex,
audioTracks.length,
mediaSources.length,
]);
// Navigation handlers // Navigation handlers
const handleActorPress = useCallback( const handleActorPress = useCallback(
(personId: string) => { (personId: string) => {
@@ -658,21 +635,17 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
{/* Playback options */} {/* Playback options */}
<View <View
style={{ style={{
flexDirection: "column", flexDirection: "row",
alignItems: "flex-start", alignItems: "center",
gap: 10, gap: 12,
marginBottom: 24, marginBottom: 24,
}} }}
> >
{/* Quality selector */} {/* Quality selector */}
<TVOptionButton <TVOptionButton
ref={
lastOptionButton === "quality"
? setLastOptionButtonRef
: undefined
}
label={t("item_card.quality")} label={t("item_card.quality")}
value={selectedQualityLabel} value={selectedQualityLabel}
maxWidth={200}
onPress={() => onPress={() =>
showOptions({ showOptions({
title: t("item_card.quality"), title: t("item_card.quality"),
@@ -685,13 +658,9 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
{/* Media source selector (only if multiple sources) */} {/* Media source selector (only if multiple sources) */}
{mediaSources.length > 1 && ( {mediaSources.length > 1 && (
<TVOptionButton <TVOptionButton
ref={
lastOptionButton === "mediaSource"
? setLastOptionButtonRef
: undefined
}
label={t("item_card.video")} label={t("item_card.video")}
value={selectedMediaSourceLabel} value={selectedMediaSourceLabel}
maxWidth={280}
onPress={() => onPress={() =>
showOptions({ showOptions({
title: t("item_card.video"), title: t("item_card.video"),
@@ -705,13 +674,9 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
{/* Audio selector */} {/* Audio selector */}
{audioTracks.length > 0 && ( {audioTracks.length > 0 && (
<TVOptionButton <TVOptionButton
ref={
lastOptionButton === "audio"
? setLastOptionButtonRef
: undefined
}
label={t("item_card.audio")} label={t("item_card.audio")}
value={selectedAudioLabel} value={selectedAudioLabel}
maxWidth={280}
onPress={() => onPress={() =>
showOptions({ showOptions({
title: t("item_card.audio"), title: t("item_card.audio"),
@@ -726,13 +691,9 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
{(subtitleStreams.length > 0 || {(subtitleStreams.length > 0 ||
selectedOptions?.subtitleIndex !== undefined) && ( selectedOptions?.subtitleIndex !== undefined) && (
<TVOptionButton <TVOptionButton
ref={
lastOptionButton === "subtitle"
? setLastOptionButtonRef
: undefined
}
label={t("item_card.subtitles.label")} label={t("item_card.subtitles.label")}
value={selectedSubtitleLabel} value={selectedSubtitleLabel}
maxWidth={280}
onPress={() => onPress={() =>
showSubtitleModal({ showSubtitleModal({
item, item,

View File

@@ -10,10 +10,11 @@ export interface TVOptionButtonProps {
value: string; value: string;
onPress: () => void; onPress: () => void;
hasTVPreferredFocus?: boolean; hasTVPreferredFocus?: boolean;
maxWidth?: number;
} }
export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>( export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>(
({ label, value, onPress, hasTVPreferredFocus }, ref) => { ({ label, value, onPress, hasTVPreferredFocus, maxWidth }, ref) => {
const typography = useScaledTVTypography(); const typography = useScaledTVTypography();
const { focused, handleFocus, handleBlur, animatedStyle } = const { focused, handleFocus, handleBlur, animatedStyle } =
useTVFocusAnimation({ scaleAmount: 1.02, duration: 120 }); useTVFocusAnimation({ scaleAmount: 1.02, duration: 120 });
@@ -47,12 +48,14 @@ export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>(
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
gap: 8, gap: 8,
maxWidth,
}} }}
> >
<Text <Text
style={{ style={{
fontSize: typography.callout, fontSize: typography.callout,
color: "#444", color: "#444",
flexShrink: 0,
}} }}
> >
{label} {label}
@@ -62,6 +65,7 @@ export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>(
fontSize: typography.callout, fontSize: typography.callout,
color: "#000", color: "#000",
fontWeight: "500", fontWeight: "500",
flexShrink: 1,
}} }}
numberOfLines={1} numberOfLines={1}
> >
@@ -75,6 +79,7 @@ export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>(
style={{ style={{
borderRadius: 8, borderRadius: 8,
overflow: "hidden", overflow: "hidden",
maxWidth,
}} }}
> >
<View <View
@@ -91,6 +96,7 @@ export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>(
style={{ style={{
fontSize: typography.callout, fontSize: typography.callout,
color: "#bbb", color: "#bbb",
flexShrink: 0,
}} }}
> >
{label} {label}
@@ -100,6 +106,7 @@ export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>(
fontSize: typography.callout, fontSize: typography.callout,
color: "#E5E7EB", color: "#E5E7EB",
fontWeight: "500", fontWeight: "500",
flexShrink: 1,
}} }}
numberOfLines={1} numberOfLines={1}
> >