diff --git a/app/(auth)/(tabs)/(home)/settings.tv.tsx b/app/(auth)/(tabs)/(home)/settings.tv.tsx index 24a08648..518e6ad1 100644 --- a/app/(auth)/(tabs)/(home)/settings.tv.tsx +++ b/app/(auth)/(tabs)/(home)/settings.tv.tsx @@ -274,112 +274,71 @@ const TVSettingsStepper: React.FC<{ ); }; -// TV-optimized dropdown selector -const TVSettingsDropdown: React.FC<{ +// TV-optimized horizontal selector - navigate left/right through options +const TVSettingsSelector: React.FC<{ label: string; options: { label: string; value: string }[]; selectedValue: string; onSelect: (value: string) => void; isFirst?: boolean; }> = ({ label, options, selectedValue, onSelect, isFirst }) => { - const [expanded, setExpanded] = useState(false); - const [focused, setFocused] = useState(false); - const scale = useRef(new Animated.Value(1)).current; + const [rowFocused, setRowFocused] = useState(false); - const animateTo = (v: number) => - Animated.timing(scale, { - toValue: v, - duration: 150, - easing: Easing.out(Easing.quad), - useNativeDriver: true, - }).start(); + const currentIndex = options.findIndex((o) => o.value === selectedValue); - const selectedLabel = - options.find((o) => o.value === selectedValue)?.label || selectedValue; - - if (expanded) { - return ( - + + {label} + + - - {label} - {options.map((option, index) => ( - { - onSelect(option.value); - setExpanded(false); - }} - isFirst={index === 0} + onSelect={() => onSelect(option.value)} + onFocus={() => setRowFocused(true)} + onBlur={() => setRowFocused(false)} + isFirst={isFirst && index === currentIndex} /> ))} - ); - } - - return ( - setExpanded(true)} - onFocus={() => { - setFocused(true); - animateTo(1.02); - }} - onBlur={() => { - setFocused(false); - animateTo(1); - }} - hasTVPreferredFocus={isFirst} - > - - {label} - - - {selectedLabel} - - - - - + ); }; -// Dropdown option component -const TVDropdownOption: React.FC<{ +// Individual option button for horizontal selector +const TVSelectorOption: React.FC<{ label: string; selected: boolean; onSelect: () => void; + onFocus: () => void; + onBlur: () => void; isFirst?: boolean; -}> = ({ label, selected, onSelect, isFirst }) => { +}> = ({ label, selected, onSelect, onFocus, onBlur, isFirst }) => { const [focused, setFocused] = useState(false); const scale = useRef(new Animated.Value(1)).current; @@ -396,10 +355,12 @@ const TVDropdownOption: React.FC<{ onPress={onSelect} onFocus={() => { setFocused(true); - animateTo(1.02); + onFocus(); + animateTo(1.08); }} onBlur={() => { setFocused(false); + onBlur(); animateTo(1); }} hasTVPreferredFocus={isFirst} @@ -407,16 +368,27 @@ const TVDropdownOption: React.FC<{ - {label} - {selected && } + + {label} + ); @@ -569,7 +541,7 @@ export default function SettingsTV() { - - - - )} - {isMounted === true && item && !isPipMode && ( - - )} + {isMounted === true && + item && + !isPipMode && + (Platform.isTV ? ( + + ) : ( + + ))} diff --git a/components/ItemContentSkeleton.tv.tsx b/components/ItemContentSkeleton.tv.tsx new file mode 100644 index 00000000..2e4a77ea --- /dev/null +++ b/components/ItemContentSkeleton.tv.tsx @@ -0,0 +1,160 @@ +import React from "react"; +import { Dimensions, View } from "react-native"; + +const { width: SCREEN_WIDTH } = Dimensions.get("window"); + +export const ItemContentSkeletonTV: React.FC = () => { + return ( + + {/* Left side - Poster placeholder */} + + + + + {/* Right side - Content placeholders */} + + {/* Logo/Title placeholder */} + + + {/* Metadata badges row */} + + + + + + + {/* Genres placeholder */} + + + + + + + {/* Overview placeholder */} + + + + + + + {/* Play button placeholder */} + + + + ); +};