mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-01-31 15:38:08 +00:00
feat(tv): change playback options layout to horizontal row
This commit is contained in:
@@ -184,10 +184,6 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
|
||||
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
|
||||
const audioTracks = useMemo(() => {
|
||||
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`;
|
||||
}, [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
|
||||
const handleActorPress = useCallback(
|
||||
(personId: string) => {
|
||||
@@ -658,21 +635,17 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
|
||||
{/* Playback options */}
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-start",
|
||||
gap: 10,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 12,
|
||||
marginBottom: 24,
|
||||
}}
|
||||
>
|
||||
{/* Quality selector */}
|
||||
<TVOptionButton
|
||||
ref={
|
||||
lastOptionButton === "quality"
|
||||
? setLastOptionButtonRef
|
||||
: undefined
|
||||
}
|
||||
label={t("item_card.quality")}
|
||||
value={selectedQualityLabel}
|
||||
maxWidth={200}
|
||||
onPress={() =>
|
||||
showOptions({
|
||||
title: t("item_card.quality"),
|
||||
@@ -685,13 +658,9 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
|
||||
{/* Media source selector (only if multiple sources) */}
|
||||
{mediaSources.length > 1 && (
|
||||
<TVOptionButton
|
||||
ref={
|
||||
lastOptionButton === "mediaSource"
|
||||
? setLastOptionButtonRef
|
||||
: undefined
|
||||
}
|
||||
label={t("item_card.video")}
|
||||
value={selectedMediaSourceLabel}
|
||||
maxWidth={280}
|
||||
onPress={() =>
|
||||
showOptions({
|
||||
title: t("item_card.video"),
|
||||
@@ -705,13 +674,9 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
|
||||
{/* Audio selector */}
|
||||
{audioTracks.length > 0 && (
|
||||
<TVOptionButton
|
||||
ref={
|
||||
lastOptionButton === "audio"
|
||||
? setLastOptionButtonRef
|
||||
: undefined
|
||||
}
|
||||
label={t("item_card.audio")}
|
||||
value={selectedAudioLabel}
|
||||
maxWidth={280}
|
||||
onPress={() =>
|
||||
showOptions({
|
||||
title: t("item_card.audio"),
|
||||
@@ -726,13 +691,9 @@ export const ItemContentTV: React.FC<ItemContentTVProps> = React.memo(
|
||||
{(subtitleStreams.length > 0 ||
|
||||
selectedOptions?.subtitleIndex !== undefined) && (
|
||||
<TVOptionButton
|
||||
ref={
|
||||
lastOptionButton === "subtitle"
|
||||
? setLastOptionButtonRef
|
||||
: undefined
|
||||
}
|
||||
label={t("item_card.subtitles.label")}
|
||||
value={selectedSubtitleLabel}
|
||||
maxWidth={280}
|
||||
onPress={() =>
|
||||
showSubtitleModal({
|
||||
item,
|
||||
|
||||
@@ -10,10 +10,11 @@ export interface TVOptionButtonProps {
|
||||
value: string;
|
||||
onPress: () => void;
|
||||
hasTVPreferredFocus?: boolean;
|
||||
maxWidth?: number;
|
||||
}
|
||||
|
||||
export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>(
|
||||
({ label, value, onPress, hasTVPreferredFocus }, ref) => {
|
||||
({ label, value, onPress, hasTVPreferredFocus, maxWidth }, ref) => {
|
||||
const typography = useScaledTVTypography();
|
||||
const { focused, handleFocus, handleBlur, animatedStyle } =
|
||||
useTVFocusAnimation({ scaleAmount: 1.02, duration: 120 });
|
||||
@@ -47,12 +48,14 @@ export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>(
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 8,
|
||||
maxWidth,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: typography.callout,
|
||||
color: "#444",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
@@ -62,6 +65,7 @@ export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>(
|
||||
fontSize: typography.callout,
|
||||
color: "#000",
|
||||
fontWeight: "500",
|
||||
flexShrink: 1,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
@@ -75,6 +79,7 @@ export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>(
|
||||
style={{
|
||||
borderRadius: 8,
|
||||
overflow: "hidden",
|
||||
maxWidth,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@@ -91,6 +96,7 @@ export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>(
|
||||
style={{
|
||||
fontSize: typography.callout,
|
||||
color: "#bbb",
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
@@ -100,6 +106,7 @@ export const TVOptionButton = React.forwardRef<View, TVOptionButtonProps>(
|
||||
fontSize: typography.callout,
|
||||
color: "#E5E7EB",
|
||||
fontWeight: "500",
|
||||
flexShrink: 1,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user