mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-09 15:38:39 +01:00
style(tv): update settings to use apple tv-style white and green accents
This commit is contained in:
@@ -7,7 +7,8 @@ import { Animated, Pressable, ScrollView, TextInput, View } from "react-native";
|
|||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import type { TVOptionItem } from "@/components/tv";
|
import type { TVOptionItem } from "@/components/tv";
|
||||||
import { TVOptionSelector, useTVFocusAnimation } from "@/components/tv";
|
import { useTVFocusAnimation } from "@/components/tv";
|
||||||
|
import { useTVOptionModal } from "@/hooks/useTVOptionModal";
|
||||||
import { apiAtom, useJellyfin, userAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom, useJellyfin, userAtom } from "@/providers/JellyfinProvider";
|
||||||
import { AudioTranscodeMode, useSettings } from "@/utils/atoms/settings";
|
import { AudioTranscodeMode, useSettings } from "@/utils/atoms/settings";
|
||||||
|
|
||||||
@@ -112,7 +113,7 @@ const TVSettingsToggle: React.FC<{
|
|||||||
width: 56,
|
width: 56,
|
||||||
height: 32,
|
height: 32,
|
||||||
borderRadius: 16,
|
borderRadius: 16,
|
||||||
backgroundColor: value ? "#7c3aed" : "#4B5563",
|
backgroundColor: value ? "#34C759" : "#4B5563",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
paddingHorizontal: 2,
|
paddingHorizontal: 2,
|
||||||
}}
|
}}
|
||||||
@@ -195,16 +196,20 @@ const TVSettingsStepper: React.FC<{
|
|||||||
style={[
|
style={[
|
||||||
minusAnim.animatedStyle,
|
minusAnim.animatedStyle,
|
||||||
{
|
{
|
||||||
width: 40,
|
width: 48,
|
||||||
height: 40,
|
height: 36,
|
||||||
borderRadius: 20,
|
borderRadius: 18,
|
||||||
backgroundColor: minusAnim.focused ? "#7c3aed" : "#4B5563",
|
backgroundColor: minusAnim.focused ? "#FFFFFF" : "#4B5563",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Ionicons name='remove' size={24} color='#FFFFFF' />
|
<Ionicons
|
||||||
|
name='remove'
|
||||||
|
size={24}
|
||||||
|
color={minusAnim.focused ? "#000000" : "#FFFFFF"}
|
||||||
|
/>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<Text
|
<Text
|
||||||
@@ -229,16 +234,20 @@ const TVSettingsStepper: React.FC<{
|
|||||||
style={[
|
style={[
|
||||||
plusAnim.animatedStyle,
|
plusAnim.animatedStyle,
|
||||||
{
|
{
|
||||||
width: 40,
|
width: 48,
|
||||||
height: 40,
|
height: 36,
|
||||||
borderRadius: 20,
|
borderRadius: 18,
|
||||||
backgroundColor: plusAnim.focused ? "#7c3aed" : "#4B5563",
|
backgroundColor: plusAnim.focused ? "#FFFFFF" : "#4B5563",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Ionicons name='add' size={24} color='#FFFFFF' />
|
<Ionicons
|
||||||
|
name='add'
|
||||||
|
size={24}
|
||||||
|
color={plusAnim.focused ? "#000000" : "#FFFFFF"}
|
||||||
|
/>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
</View>
|
</View>
|
||||||
@@ -371,7 +380,7 @@ const TVSettingsTextInput: React.FC<{
|
|||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
borderWidth: focused ? 2 : 1,
|
borderWidth: focused ? 2 : 1,
|
||||||
borderColor: focused ? "#7c3aed" : "#4B5563",
|
borderColor: focused ? "#FFFFFF" : "#4B5563",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
@@ -450,14 +459,6 @@ const TVLogoutButton: React.FC<{ onPress: () => void; disabled?: boolean }> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Modal type for tracking open bottom sheets
|
|
||||||
type SettingsModalType =
|
|
||||||
| "audioTranscode"
|
|
||||||
| "subtitleMode"
|
|
||||||
| "alignX"
|
|
||||||
| "alignY"
|
|
||||||
| null;
|
|
||||||
|
|
||||||
export default function SettingsTV() {
|
export default function SettingsTV() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
@@ -465,9 +466,7 @@ export default function SettingsTV() {
|
|||||||
const { logout } = useJellyfin();
|
const { logout } = useJellyfin();
|
||||||
const [user] = useAtom(userAtom);
|
const [user] = useAtom(userAtom);
|
||||||
const [api] = useAtom(apiAtom);
|
const [api] = useAtom(apiAtom);
|
||||||
|
const { showOptions } = useTVOptionModal();
|
||||||
// Modal state for option selectors
|
|
||||||
const [openModal, setOpenModal] = useState<SettingsModalType>(null);
|
|
||||||
|
|
||||||
// Local state for OpenSubtitles API key (only commit on blur)
|
// Local state for OpenSubtitles API key (only commit on blur)
|
||||||
const [openSubtitlesApiKey, setOpenSubtitlesApiKey] = useState(
|
const [openSubtitlesApiKey, setOpenSubtitlesApiKey] = useState(
|
||||||
@@ -592,18 +591,9 @@ export default function SettingsTV() {
|
|||||||
return option?.label || "Bottom";
|
return option?.label || "Bottom";
|
||||||
}, [alignYOptions]);
|
}, [alignYOptions]);
|
||||||
|
|
||||||
const isModalOpen = openModal !== null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, backgroundColor: "#000000" }}>
|
<View style={{ flex: 1, backgroundColor: "#000000" }}>
|
||||||
<View
|
<View style={{ flex: 1 }}>
|
||||||
style={{ flex: 1, opacity: isModalOpen ? 0.3 : 1 }}
|
|
||||||
focusable={!isModalOpen}
|
|
||||||
isTVSelectable={!isModalOpen}
|
|
||||||
pointerEvents={isModalOpen ? "none" : "auto"}
|
|
||||||
accessibilityElementsHidden={isModalOpen}
|
|
||||||
importantForAccessibility={isModalOpen ? "no-hide-descendants" : "auto"}
|
|
||||||
>
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
contentContainerStyle={{
|
contentContainerStyle={{
|
||||||
@@ -630,9 +620,15 @@ export default function SettingsTV() {
|
|||||||
<TVSettingsOptionButton
|
<TVSettingsOptionButton
|
||||||
label={t("home.settings.audio.transcode_mode.title")}
|
label={t("home.settings.audio.transcode_mode.title")}
|
||||||
value={audioTranscodeLabel}
|
value={audioTranscodeLabel}
|
||||||
onPress={() => setOpenModal("audioTranscode")}
|
onPress={() =>
|
||||||
|
showOptions({
|
||||||
|
title: t("home.settings.audio.transcode_mode.title"),
|
||||||
|
options: audioTranscodeModeOptions,
|
||||||
|
onSelect: (value) =>
|
||||||
|
updateSettings({ audioTranscodeMode: value }),
|
||||||
|
})
|
||||||
|
}
|
||||||
isFirst
|
isFirst
|
||||||
disabled={isModalOpen}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Subtitles Section */}
|
{/* Subtitles Section */}
|
||||||
@@ -640,8 +636,13 @@ export default function SettingsTV() {
|
|||||||
<TVSettingsOptionButton
|
<TVSettingsOptionButton
|
||||||
label={t("home.settings.subtitles.subtitle_mode")}
|
label={t("home.settings.subtitles.subtitle_mode")}
|
||||||
value={subtitleModeLabel}
|
value={subtitleModeLabel}
|
||||||
onPress={() => setOpenModal("subtitleMode")}
|
onPress={() =>
|
||||||
disabled={isModalOpen}
|
showOptions({
|
||||||
|
title: t("home.settings.subtitles.subtitle_mode"),
|
||||||
|
options: subtitleModeOptions,
|
||||||
|
onSelect: (value) => updateSettings({ subtitleMode: value }),
|
||||||
|
})
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<TVSettingsToggle
|
<TVSettingsToggle
|
||||||
label={t("home.settings.subtitles.set_subtitle_track")}
|
label={t("home.settings.subtitles.set_subtitle_track")}
|
||||||
@@ -649,7 +650,6 @@ export default function SettingsTV() {
|
|||||||
onToggle={(value) =>
|
onToggle={(value) =>
|
||||||
updateSettings({ rememberSubtitleSelections: value })
|
updateSettings({ rememberSubtitleSelections: value })
|
||||||
}
|
}
|
||||||
disabled={isModalOpen}
|
|
||||||
/>
|
/>
|
||||||
<TVSettingsStepper
|
<TVSettingsStepper
|
||||||
label={t("home.settings.subtitles.subtitle_size")}
|
label={t("home.settings.subtitles.subtitle_size")}
|
||||||
@@ -663,7 +663,6 @@ export default function SettingsTV() {
|
|||||||
updateSettings({ subtitleSize: Math.round(newValue * 100) });
|
updateSettings({ subtitleSize: Math.round(newValue * 100) });
|
||||||
}}
|
}}
|
||||||
formatValue={(v) => `${v.toFixed(1)}x`}
|
formatValue={(v) => `${v.toFixed(1)}x`}
|
||||||
disabled={isModalOpen}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* MPV Subtitles Section */}
|
{/* MPV Subtitles Section */}
|
||||||
@@ -690,7 +689,6 @@ export default function SettingsTV() {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
formatValue={(v) => `${v.toFixed(1)}x`}
|
formatValue={(v) => `${v.toFixed(1)}x`}
|
||||||
disabled={isModalOpen}
|
|
||||||
/>
|
/>
|
||||||
<TVSettingsStepper
|
<TVSettingsStepper
|
||||||
label='Vertical Margin'
|
label='Vertical Margin'
|
||||||
@@ -709,19 +707,34 @@ export default function SettingsTV() {
|
|||||||
);
|
);
|
||||||
updateSettings({ mpvSubtitleMarginY: newValue });
|
updateSettings({ mpvSubtitleMarginY: newValue });
|
||||||
}}
|
}}
|
||||||
disabled={isModalOpen}
|
|
||||||
/>
|
/>
|
||||||
<TVSettingsOptionButton
|
<TVSettingsOptionButton
|
||||||
label='Horizontal Alignment'
|
label='Horizontal Alignment'
|
||||||
value={alignXLabel}
|
value={alignXLabel}
|
||||||
onPress={() => setOpenModal("alignX")}
|
onPress={() =>
|
||||||
disabled={isModalOpen}
|
showOptions({
|
||||||
|
title: "Horizontal Alignment",
|
||||||
|
options: alignXOptions,
|
||||||
|
onSelect: (value) =>
|
||||||
|
updateSettings({
|
||||||
|
mpvSubtitleAlignX: value as "left" | "center" | "right",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<TVSettingsOptionButton
|
<TVSettingsOptionButton
|
||||||
label='Vertical Alignment'
|
label='Vertical Alignment'
|
||||||
value={alignYLabel}
|
value={alignYLabel}
|
||||||
onPress={() => setOpenModal("alignY")}
|
onPress={() =>
|
||||||
disabled={isModalOpen}
|
showOptions({
|
||||||
|
title: "Vertical Alignment",
|
||||||
|
options: alignYOptions,
|
||||||
|
onSelect: (value) =>
|
||||||
|
updateSettings({
|
||||||
|
mpvSubtitleAlignY: value as "top" | "center" | "bottom",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* OpenSubtitles Section */}
|
{/* OpenSubtitles Section */}
|
||||||
@@ -754,7 +767,6 @@ export default function SettingsTV() {
|
|||||||
onChangeText={setOpenSubtitlesApiKey}
|
onChangeText={setOpenSubtitlesApiKey}
|
||||||
onBlur={() => updateSettings({ openSubtitlesApiKey })}
|
onBlur={() => updateSettings({ openSubtitlesApiKey })}
|
||||||
secureTextEntry
|
secureTextEntry
|
||||||
disabled={isModalOpen}
|
|
||||||
/>
|
/>
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
@@ -778,13 +790,11 @@ export default function SettingsTV() {
|
|||||||
onToggle={(value) =>
|
onToggle={(value) =>
|
||||||
updateSettings({ mergeNextUpAndContinueWatching: value })
|
updateSettings({ mergeNextUpAndContinueWatching: value })
|
||||||
}
|
}
|
||||||
disabled={isModalOpen}
|
|
||||||
/>
|
/>
|
||||||
<TVSettingsToggle
|
<TVSettingsToggle
|
||||||
label={t("home.settings.appearance.show_home_backdrop")}
|
label={t("home.settings.appearance.show_home_backdrop")}
|
||||||
value={settings.showHomeBackdrop}
|
value={settings.showHomeBackdrop}
|
||||||
onToggle={(value) => updateSettings({ showHomeBackdrop: value })}
|
onToggle={(value) => updateSettings({ showHomeBackdrop: value })}
|
||||||
disabled={isModalOpen}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* User Section */}
|
{/* User Section */}
|
||||||
@@ -793,62 +803,19 @@ export default function SettingsTV() {
|
|||||||
label={t("home.settings.user_info.user")}
|
label={t("home.settings.user_info.user")}
|
||||||
value={user?.Name || "-"}
|
value={user?.Name || "-"}
|
||||||
showChevron={false}
|
showChevron={false}
|
||||||
disabled={isModalOpen}
|
|
||||||
/>
|
/>
|
||||||
<TVSettingsRow
|
<TVSettingsRow
|
||||||
label={t("home.settings.user_info.server")}
|
label={t("home.settings.user_info.server")}
|
||||||
value={api?.basePath || "-"}
|
value={api?.basePath || "-"}
|
||||||
showChevron={false}
|
showChevron={false}
|
||||||
disabled={isModalOpen}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Logout Button */}
|
{/* Logout Button */}
|
||||||
<View style={{ marginTop: 48, alignItems: "center" }}>
|
<View style={{ marginTop: 48, alignItems: "center" }}>
|
||||||
<TVLogoutButton onPress={logout} disabled={isModalOpen} />
|
<TVLogoutButton onPress={logout} />
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Bottom sheet modals using shared TVOptionSelector */}
|
|
||||||
<TVOptionSelector
|
|
||||||
visible={openModal === "audioTranscode"}
|
|
||||||
title={t("home.settings.audio.transcode_mode.title")}
|
|
||||||
options={audioTranscodeModeOptions}
|
|
||||||
onSelect={(value) => updateSettings({ audioTranscodeMode: value })}
|
|
||||||
onClose={() => setOpenModal(null)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TVOptionSelector
|
|
||||||
visible={openModal === "subtitleMode"}
|
|
||||||
title={t("home.settings.subtitles.subtitle_mode")}
|
|
||||||
options={subtitleModeOptions}
|
|
||||||
onSelect={(value) => updateSettings({ subtitleMode: value })}
|
|
||||||
onClose={() => setOpenModal(null)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TVOptionSelector
|
|
||||||
visible={openModal === "alignX"}
|
|
||||||
title='Horizontal Alignment'
|
|
||||||
options={alignXOptions}
|
|
||||||
onSelect={(value) =>
|
|
||||||
updateSettings({
|
|
||||||
mpvSubtitleAlignX: value as "left" | "center" | "right",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onClose={() => setOpenModal(null)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TVOptionSelector
|
|
||||||
visible={openModal === "alignY"}
|
|
||||||
title='Vertical Alignment'
|
|
||||||
options={alignYOptions}
|
|
||||||
onSelect={(value) =>
|
|
||||||
updateSettings({
|
|
||||||
mpvSubtitleAlignY: value as "top" | "center" | "bottom",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onClose={() => setOpenModal(null)}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user