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 */}
+
+
+
+ );
+};