mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-05-03 05:46:28 +01: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,
|
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,
|
||||||
|
|||||||
@@ -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}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user