mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-26 15:50:26 +01:00
chore: more scaling fixes and selection improve
Fixed the scaling in the direct player controls to use the scaleTV settings Fixed 2 items in settings not being selectable (added style:flex:1) Signed-off-by: Lance Chant <13349722+lancechant@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { BlurView } from "expo-blur";
|
import { BlurView } from "expo-blur";
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Animated,
|
Animated,
|
||||||
Easing,
|
Easing,
|
||||||
@@ -11,13 +11,17 @@ import {
|
|||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { TVOptionCard } from "@/components/tv";
|
import { TVOptionCard } from "@/components/tv";
|
||||||
|
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||||
import useRouter from "@/hooks/useAppRouter";
|
import useRouter from "@/hooks/useAppRouter";
|
||||||
|
import { useTVBackPress } from "@/hooks/useTVBackPress";
|
||||||
import { tvOptionModalAtom } from "@/utils/atoms/tvOptionModal";
|
import { tvOptionModalAtom } from "@/utils/atoms/tvOptionModal";
|
||||||
|
import { scaleSize } from "@/utils/scaleSize";
|
||||||
import { store } from "@/utils/store";
|
import { store } from "@/utils/store";
|
||||||
|
|
||||||
export default function TVOptionModal() {
|
export default function TVOptionModal() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const modalState = useAtomValue(tvOptionModalAtom);
|
const modalState = useAtomValue(tvOptionModalAtom);
|
||||||
|
const typography = useScaledTVTypography();
|
||||||
|
|
||||||
const [isReady, setIsReady] = useState(false);
|
const [isReady, setIsReady] = useState(false);
|
||||||
const firstCardRef = useRef<View>(null);
|
const firstCardRef = useRef<View>(null);
|
||||||
@@ -76,12 +80,25 @@ export default function TVOptionModal() {
|
|||||||
router.back();
|
router.back();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
store.set(tvOptionModalAtom, null);
|
||||||
|
router.back();
|
||||||
|
}, [router]);
|
||||||
|
|
||||||
|
// Intercept back/menu press to close the modal instead of the player
|
||||||
|
useTVBackPress(() => {
|
||||||
|
handleClose();
|
||||||
|
return true;
|
||||||
|
}, [handleClose]);
|
||||||
|
|
||||||
// If no modal state, just go back (shouldn't happen in normal usage)
|
// If no modal state, just go back (shouldn't happen in normal usage)
|
||||||
if (!modalState) {
|
if (!modalState) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title, options, cardWidth = 160, cardHeight = 75 } = modalState;
|
const { title, options } = modalState;
|
||||||
|
const scaledCardWidth = scaleSize(160);
|
||||||
|
const scaledCardHeight = scaleSize(75);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Animated.View style={[styles.overlay, { opacity: overlayOpacity }]}>
|
<Animated.View style={[styles.overlay, { opacity: overlayOpacity }]}>
|
||||||
@@ -100,7 +117,9 @@ export default function TVOptionModal() {
|
|||||||
trapFocusRight
|
trapFocusRight
|
||||||
style={styles.content}
|
style={styles.content}
|
||||||
>
|
>
|
||||||
<Text style={styles.title}>{title}</Text>
|
<Text style={[styles.title, { fontSize: typography.callout }]}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
{isReady && (
|
{isReady && (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
horizontal
|
horizontal
|
||||||
@@ -119,8 +138,8 @@ export default function TVOptionModal() {
|
|||||||
selected={option.selected}
|
selected={option.selected}
|
||||||
hasTVPreferredFocus={index === initialSelectedIndex}
|
hasTVPreferredFocus={index === initialSelectedIndex}
|
||||||
onPress={() => handleSelect(option.value)}
|
onPress={() => handleSelect(option.value)}
|
||||||
width={cardWidth}
|
width={scaledCardWidth}
|
||||||
height={cardHeight}
|
height={scaledCardHeight}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
@@ -142,21 +161,20 @@ const styles = StyleSheet.create({
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
},
|
},
|
||||||
blurContainer: {
|
blurContainer: {
|
||||||
borderTopLeftRadius: 24,
|
borderTopLeftRadius: scaleSize(24),
|
||||||
borderTopRightRadius: 24,
|
borderTopRightRadius: scaleSize(24),
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
paddingTop: 24,
|
paddingTop: scaleSize(24),
|
||||||
paddingBottom: 50,
|
paddingBottom: scaleSize(50),
|
||||||
overflow: "visible",
|
overflow: "visible",
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: "500",
|
fontWeight: "500",
|
||||||
color: "rgba(255,255,255,0.6)",
|
color: "rgba(255,255,255,0.6)",
|
||||||
marginBottom: 16,
|
marginBottom: scaleSize(16),
|
||||||
paddingHorizontal: 48,
|
paddingHorizontal: scaleSize(48),
|
||||||
textTransform: "uppercase",
|
textTransform: "uppercase",
|
||||||
letterSpacing: 1,
|
letterSpacing: 1,
|
||||||
},
|
},
|
||||||
@@ -164,8 +182,8 @@ const styles = StyleSheet.create({
|
|||||||
overflow: "visible",
|
overflow: "visible",
|
||||||
},
|
},
|
||||||
scrollContent: {
|
scrollContent: {
|
||||||
paddingHorizontal: 48,
|
paddingHorizontal: scaleSize(48),
|
||||||
paddingVertical: 20,
|
paddingVertical: scaleSize(20),
|
||||||
gap: 12,
|
gap: scaleSize(12),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,14 +22,17 @@ import {
|
|||||||
import { Text } from "@/components/common/Text";
|
import { Text } from "@/components/common/Text";
|
||||||
import { TVTabButton, useTVFocusAnimation } from "@/components/tv";
|
import { TVTabButton, useTVFocusAnimation } from "@/components/tv";
|
||||||
import type { Track } from "@/components/video-player/controls/types";
|
import type { Track } from "@/components/video-player/controls/types";
|
||||||
|
import { useScaledTVTypography } from "@/constants/TVTypography";
|
||||||
import useRouter from "@/hooks/useAppRouter";
|
import useRouter from "@/hooks/useAppRouter";
|
||||||
import {
|
import {
|
||||||
type SubtitleSearchResult,
|
type SubtitleSearchResult,
|
||||||
useRemoteSubtitles,
|
useRemoteSubtitles,
|
||||||
} from "@/hooks/useRemoteSubtitles";
|
} from "@/hooks/useRemoteSubtitles";
|
||||||
|
import { useTVBackPress } from "@/hooks/useTVBackPress";
|
||||||
import { useSettings } from "@/utils/atoms/settings";
|
import { useSettings } from "@/utils/atoms/settings";
|
||||||
import { tvSubtitleModalAtom } from "@/utils/atoms/tvSubtitleModal";
|
import { tvSubtitleModalAtom } from "@/utils/atoms/tvSubtitleModal";
|
||||||
import { COMMON_SUBTITLE_LANGUAGES } from "@/utils/opensubtitles/api";
|
import { COMMON_SUBTITLE_LANGUAGES } from "@/utils/opensubtitles/api";
|
||||||
|
import { scaleSize } from "@/utils/scaleSize";
|
||||||
import { store } from "@/utils/store";
|
import { store } from "@/utils/store";
|
||||||
|
|
||||||
type TabType = "tracks" | "download" | "settings";
|
type TabType = "tracks" | "download" | "settings";
|
||||||
@@ -72,10 +75,10 @@ const TVTrackCard = React.forwardRef<
|
|||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
styles.trackCardText,
|
styles.trackCardText,
|
||||||
{ color: focused ? "#000" : "#fff" },
|
{ color: focused ? "#000" : "#fff", fontSize: scaleSize(16) },
|
||||||
(focused || selected) && { fontWeight: "600" },
|
(focused || selected) && { fontWeight: "600" },
|
||||||
]}
|
]}
|
||||||
numberOfLines={2}
|
numberOfLines={3}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -83,7 +86,10 @@ const TVTrackCard = React.forwardRef<
|
|||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
styles.trackCardSublabel,
|
styles.trackCardSublabel,
|
||||||
{ color: focused ? "rgba(0,0,0,0.6)" : "rgba(255,255,255,0.5)" },
|
{
|
||||||
|
color: focused ? "rgba(0,0,0,0.6)" : "rgba(255,255,255,0.5)",
|
||||||
|
fontSize: scaleSize(12),
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
>
|
>
|
||||||
@@ -94,7 +100,7 @@ const TVTrackCard = React.forwardRef<
|
|||||||
<View style={styles.checkmark}>
|
<View style={styles.checkmark}>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name='checkmark'
|
name='checkmark'
|
||||||
size={16}
|
size={scaleSize(16)}
|
||||||
color='rgba(255,255,255,0.8)'
|
color='rgba(255,255,255,0.8)'
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -142,7 +148,7 @@ const LanguageCard = React.forwardRef<
|
|||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
styles.languageCardText,
|
styles.languageCardText,
|
||||||
{ color: focused ? "#000" : "#fff" },
|
{ color: focused ? "#000" : "#fff", fontSize: scaleSize(15) },
|
||||||
(focused || selected) && { fontWeight: "600" },
|
(focused || selected) && { fontWeight: "600" },
|
||||||
]}
|
]}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
@@ -152,7 +158,10 @@ const LanguageCard = React.forwardRef<
|
|||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
styles.languageCardCode,
|
styles.languageCardCode,
|
||||||
{ color: focused ? "rgba(0,0,0,0.6)" : "rgba(255,255,255,0.5)" },
|
{
|
||||||
|
color: focused ? "rgba(0,0,0,0.6)" : "rgba(255,255,255,0.5)",
|
||||||
|
fontSize: scaleSize(11),
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{code.toUpperCase()}
|
{code.toUpperCase()}
|
||||||
@@ -161,7 +170,7 @@ const LanguageCard = React.forwardRef<
|
|||||||
<View style={styles.checkmark}>
|
<View style={styles.checkmark}>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name='checkmark'
|
name='checkmark'
|
||||||
size={16}
|
size={scaleSize(16)}
|
||||||
color='rgba(255,255,255,0.8)'
|
color='rgba(255,255,255,0.8)'
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -219,7 +228,10 @@ const SubtitleResultCard = React.forwardRef<
|
|||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
styles.providerText,
|
styles.providerText,
|
||||||
{ color: focused ? "rgba(0,0,0,0.7)" : "rgba(255,255,255,0.7)" },
|
{
|
||||||
|
color: focused ? "rgba(0,0,0,0.7)" : "rgba(255,255,255,0.7)",
|
||||||
|
fontSize: scaleSize(11),
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{result.providerName}
|
{result.providerName}
|
||||||
@@ -228,7 +240,10 @@ const SubtitleResultCard = React.forwardRef<
|
|||||||
|
|
||||||
{/* Name */}
|
{/* Name */}
|
||||||
<Text
|
<Text
|
||||||
style={[styles.resultName, { color: focused ? "#000" : "#fff" }]}
|
style={[
|
||||||
|
styles.resultName,
|
||||||
|
{ color: focused ? "#000" : "#fff", fontSize: scaleSize(14) },
|
||||||
|
]}
|
||||||
numberOfLines={2}
|
numberOfLines={2}
|
||||||
>
|
>
|
||||||
{result.name}
|
{result.name}
|
||||||
@@ -240,7 +255,10 @@ const SubtitleResultCard = React.forwardRef<
|
|||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
styles.resultMetaText,
|
styles.resultMetaText,
|
||||||
{ color: focused ? "rgba(0,0,0,0.6)" : "rgba(255,255,255,0.5)" },
|
{
|
||||||
|
color: focused ? "rgba(0,0,0,0.6)" : "rgba(255,255,255,0.5)",
|
||||||
|
fontSize: scaleSize(12),
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{result.format?.toUpperCase()}
|
{result.format?.toUpperCase()}
|
||||||
@@ -252,7 +270,7 @@ const SubtitleResultCard = React.forwardRef<
|
|||||||
<View style={styles.ratingContainer}>
|
<View style={styles.ratingContainer}>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name='star'
|
name='star'
|
||||||
size={12}
|
size={scaleSize(12)}
|
||||||
color={focused ? "rgba(0,0,0,0.6)" : "rgba(255,255,255,0.5)"}
|
color={focused ? "rgba(0,0,0,0.6)" : "rgba(255,255,255,0.5)"}
|
||||||
/>
|
/>
|
||||||
<Text
|
<Text
|
||||||
@@ -262,6 +280,7 @@ const SubtitleResultCard = React.forwardRef<
|
|||||||
color: focused
|
color: focused
|
||||||
? "rgba(0,0,0,0.6)"
|
? "rgba(0,0,0,0.6)"
|
||||||
: "rgba(255,255,255,0.5)",
|
: "rgba(255,255,255,0.5)",
|
||||||
|
fontSize: scaleSize(12),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -275,7 +294,7 @@ const SubtitleResultCard = React.forwardRef<
|
|||||||
<View style={styles.downloadCountContainer}>
|
<View style={styles.downloadCountContainer}>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name='download-outline'
|
name='download-outline'
|
||||||
size={12}
|
size={scaleSize(12)}
|
||||||
color={focused ? "rgba(0,0,0,0.6)" : "rgba(255,255,255,0.5)"}
|
color={focused ? "rgba(0,0,0,0.6)" : "rgba(255,255,255,0.5)"}
|
||||||
/>
|
/>
|
||||||
<Text
|
<Text
|
||||||
@@ -285,6 +304,7 @@ const SubtitleResultCard = React.forwardRef<
|
|||||||
color: focused
|
color: focused
|
||||||
? "rgba(0,0,0,0.6)"
|
? "rgba(0,0,0,0.6)"
|
||||||
: "rgba(255,255,255,0.5)",
|
: "rgba(255,255,255,0.5)",
|
||||||
|
fontSize: scaleSize(12),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -307,7 +327,9 @@ const SubtitleResultCard = React.forwardRef<
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Text style={styles.flagText}>Hash Match</Text>
|
<Text style={[styles.flagText, { fontSize: scaleSize(10) }]}>
|
||||||
|
Hash Match
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{result.hearingImpaired && (
|
{result.hearingImpaired && (
|
||||||
@@ -323,7 +345,7 @@ const SubtitleResultCard = React.forwardRef<
|
|||||||
>
|
>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name='ear-outline'
|
name='ear-outline'
|
||||||
size={12}
|
size={scaleSize(12)}
|
||||||
color={focused ? "#000" : "#fff"}
|
color={focused ? "#000" : "#fff"}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -339,7 +361,9 @@ const SubtitleResultCard = React.forwardRef<
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Text style={styles.flagText}>AI</Text>
|
<Text style={[styles.flagText, { fontSize: scaleSize(10) }]}>
|
||||||
|
AI
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@@ -389,7 +413,7 @@ const TVStepperButton: React.FC<{
|
|||||||
>
|
>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name={icon}
|
name={icon}
|
||||||
size={28}
|
size={scaleSize(28)}
|
||||||
color={focused ? "#000" : disabled ? "rgba(255,255,255,0.4)" : "#fff"}
|
color={focused ? "#000" : disabled ? "rgba(255,255,255,0.4)" : "#fff"}
|
||||||
/>
|
/>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
@@ -485,7 +509,7 @@ const TVAlignmentCard: React.FC<{
|
|||||||
<Text
|
<Text
|
||||||
style={[
|
style={[
|
||||||
styles.alignmentCardText,
|
styles.alignmentCardText,
|
||||||
{ color: focused ? "#000" : "#fff" },
|
{ color: focused ? "#000" : "#fff", fontSize: scaleSize(15) },
|
||||||
(focused || selected) && { fontWeight: "600" },
|
(focused || selected) && { fontWeight: "600" },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -495,7 +519,7 @@ const TVAlignmentCard: React.FC<{
|
|||||||
<View style={styles.alignmentCheckmark}>
|
<View style={styles.alignmentCheckmark}>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name='checkmark'
|
name='checkmark'
|
||||||
size={14}
|
size={scaleSize(14)}
|
||||||
color='rgba(255,255,255,0.8)'
|
color='rgba(255,255,255,0.8)'
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -510,6 +534,7 @@ export default function TVSubtitleModal() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const modalState = useAtomValue(tvSubtitleModalAtom);
|
const modalState = useAtomValue(tvSubtitleModalAtom);
|
||||||
const { settings, updateSettings } = useSettings();
|
const { settings, updateSettings } = useSettings();
|
||||||
|
const typography = useScaledTVTypography();
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState<TabType>("tracks");
|
const [activeTab, setActiveTab] = useState<TabType>("tracks");
|
||||||
const [selectedLanguage, setSelectedLanguage] = useState("eng");
|
const [selectedLanguage, setSelectedLanguage] = useState("eng");
|
||||||
@@ -604,6 +629,12 @@ export default function TVSubtitleModal() {
|
|||||||
router.back();
|
router.back();
|
||||||
}, [router]);
|
}, [router]);
|
||||||
|
|
||||||
|
// Intercept back/menu press to close the modal instead of the player
|
||||||
|
useTVBackPress(() => {
|
||||||
|
handleClose();
|
||||||
|
return true;
|
||||||
|
}, [handleClose]);
|
||||||
|
|
||||||
const handleLanguageSelect = useCallback(
|
const handleLanguageSelect = useCallback(
|
||||||
(code: string) => {
|
(code: string) => {
|
||||||
setSelectedLanguage(code);
|
setSelectedLanguage(code);
|
||||||
@@ -745,7 +776,7 @@ export default function TVSubtitleModal() {
|
|||||||
>
|
>
|
||||||
{/* Header with tabs */}
|
{/* Header with tabs */}
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<Text style={styles.title}>
|
<Text style={[styles.title, { fontSize: typography.heading }]}>
|
||||||
{t("item_card.subtitles.label") || "Subtitles"}
|
{t("item_card.subtitles.label") || "Subtitles"}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
@@ -802,7 +833,9 @@ export default function TVSubtitleModal() {
|
|||||||
<>
|
<>
|
||||||
{/* Language Selector */}
|
{/* Language Selector */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>
|
<Text
|
||||||
|
style={[styles.sectionTitle, { fontSize: scaleSize(14) }]}
|
||||||
|
>
|
||||||
{t("player.language") || "Language"}
|
{t("player.language") || "Language"}
|
||||||
</Text>
|
</Text>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
@@ -829,7 +862,9 @@ export default function TVSubtitleModal() {
|
|||||||
|
|
||||||
{/* Results Section */}
|
{/* Results Section */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>
|
<Text
|
||||||
|
style={[styles.sectionTitle, { fontSize: scaleSize(14) }]}
|
||||||
|
>
|
||||||
{t("player.results") || "Results"}
|
{t("player.results") || "Results"}
|
||||||
{searchResults && ` (${searchResults.length})`}
|
{searchResults && ` (${searchResults.length})`}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -846,13 +881,17 @@ export default function TVSubtitleModal() {
|
|||||||
<View style={styles.errorContainer}>
|
<View style={styles.errorContainer}>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name='alert-circle-outline'
|
name='alert-circle-outline'
|
||||||
size={32}
|
size={scaleSize(32)}
|
||||||
color='rgba(255,100,100,0.8)'
|
color='rgba(255,100,100,0.8)'
|
||||||
/>
|
/>
|
||||||
<Text style={styles.errorText}>
|
<Text
|
||||||
|
style={[styles.errorText, { fontSize: scaleSize(16) }]}
|
||||||
|
>
|
||||||
{t("player.search_failed") || "Search failed"}
|
{t("player.search_failed") || "Search failed"}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.errorHint}>
|
<Text
|
||||||
|
style={[styles.errorHint, { fontSize: scaleSize(13) }]}
|
||||||
|
>
|
||||||
{!hasOpenSubtitlesApiKey
|
{!hasOpenSubtitlesApiKey
|
||||||
? t("player.no_subtitle_provider") ||
|
? t("player.no_subtitle_provider") ||
|
||||||
"No subtitle provider configured on server"
|
"No subtitle provider configured on server"
|
||||||
@@ -869,10 +908,15 @@ export default function TVSubtitleModal() {
|
|||||||
<View style={styles.emptyContainer}>
|
<View style={styles.emptyContainer}>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name='document-text-outline'
|
name='document-text-outline'
|
||||||
size={32}
|
size={scaleSize(32)}
|
||||||
color='rgba(255,255,255,0.4)'
|
color='rgba(255,255,255,0.4)'
|
||||||
/>
|
/>
|
||||||
<Text style={styles.emptyText}>
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.emptyText,
|
||||||
|
{ fontSize: scaleSize(14) },
|
||||||
|
]}
|
||||||
|
>
|
||||||
{t("player.no_subtitles_found") ||
|
{t("player.no_subtitles_found") ||
|
||||||
"No subtitles found"}
|
"No subtitles found"}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -907,10 +951,15 @@ export default function TVSubtitleModal() {
|
|||||||
<View style={styles.apiKeyHint}>
|
<View style={styles.apiKeyHint}>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name='information-circle-outline'
|
name='information-circle-outline'
|
||||||
size={16}
|
size={scaleSize(16)}
|
||||||
color='rgba(255,255,255,0.4)'
|
color='rgba(255,255,255,0.4)'
|
||||||
/>
|
/>
|
||||||
<Text style={styles.apiKeyHintText}>
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.apiKeyHintText,
|
||||||
|
{ fontSize: scaleSize(12) },
|
||||||
|
]}
|
||||||
|
>
|
||||||
{t("player.add_opensubtitles_key_hint") ||
|
{t("player.add_opensubtitles_key_hint") ||
|
||||||
"Add OpenSubtitles API key in settings for client-side fallback"}
|
"Add OpenSubtitles API key in settings for client-side fallback"}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -942,7 +991,12 @@ export default function TVSubtitleModal() {
|
|||||||
}}
|
}}
|
||||||
hasTVPreferredFocus={true}
|
hasTVPreferredFocus={true}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.settingLabel}>
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.settingLabel,
|
||||||
|
{ fontSize: typography.callout },
|
||||||
|
]}
|
||||||
|
>
|
||||||
{t("home.settings.subtitles.mpv_subtitle_scale") ||
|
{t("home.settings.subtitles.mpv_subtitle_scale") ||
|
||||||
"Subtitle Scale"}
|
"Subtitle Scale"}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -960,7 +1014,12 @@ export default function TVSubtitleModal() {
|
|||||||
updateSettings({ mpvSubtitleMarginY: newValue });
|
updateSettings({ mpvSubtitleMarginY: newValue });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.settingLabel}>
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.settingLabel,
|
||||||
|
{ fontSize: typography.callout },
|
||||||
|
]}
|
||||||
|
>
|
||||||
{t("home.settings.subtitles.mpv_subtitle_margin_y") ||
|
{t("home.settings.subtitles.mpv_subtitle_margin_y") ||
|
||||||
"Vertical Margin"}
|
"Vertical Margin"}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -984,7 +1043,12 @@ export default function TVSubtitleModal() {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.settingLabel}>
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.settingLabel,
|
||||||
|
{ fontSize: typography.callout },
|
||||||
|
]}
|
||||||
|
>
|
||||||
{t("home.settings.subtitles.mpv_subtitle_align_x") ||
|
{t("home.settings.subtitles.mpv_subtitle_align_x") ||
|
||||||
"Horizontal Align"}
|
"Horizontal Align"}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -1008,7 +1072,12 @@ export default function TVSubtitleModal() {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
<Text style={styles.settingLabel}>
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.settingLabel,
|
||||||
|
{ fontSize: typography.callout },
|
||||||
|
]}
|
||||||
|
>
|
||||||
{t("home.settings.subtitles.mpv_subtitle_align_y") ||
|
{t("home.settings.subtitles.mpv_subtitle_align_y") ||
|
||||||
"Vertical Align"}
|
"Vertical Align"}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -1033,218 +1102,201 @@ const styles = StyleSheet.create({
|
|||||||
maxHeight: "70%",
|
maxHeight: "70%",
|
||||||
},
|
},
|
||||||
blurContainer: {
|
blurContainer: {
|
||||||
borderTopLeftRadius: 24,
|
borderTopLeftRadius: scaleSize(24),
|
||||||
borderTopRightRadius: 24,
|
borderTopRightRadius: scaleSize(24),
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
paddingTop: 24,
|
paddingTop: scaleSize(24),
|
||||||
paddingBottom: 48,
|
paddingBottom: scaleSize(48),
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
paddingHorizontal: 48,
|
paddingHorizontal: scaleSize(48),
|
||||||
marginBottom: 20,
|
marginBottom: scaleSize(20),
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: "600",
|
fontWeight: "600",
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
marginBottom: 16,
|
marginBottom: scaleSize(16),
|
||||||
},
|
},
|
||||||
tabRow: {
|
tabRow: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
gap: 24,
|
gap: scaleSize(24),
|
||||||
},
|
},
|
||||||
section: {
|
section: {
|
||||||
marginBottom: 20,
|
marginBottom: scaleSize(20),
|
||||||
},
|
},
|
||||||
sectionTitle: {
|
sectionTitle: {
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: "500",
|
fontWeight: "500",
|
||||||
color: "rgba(255,255,255,0.5)",
|
color: "rgba(255,255,255,0.5)",
|
||||||
textTransform: "uppercase",
|
textTransform: "uppercase",
|
||||||
letterSpacing: 1,
|
letterSpacing: 1,
|
||||||
marginBottom: 12,
|
marginBottom: scaleSize(12),
|
||||||
paddingHorizontal: 48,
|
paddingHorizontal: scaleSize(48),
|
||||||
},
|
},
|
||||||
tracksScroll: {
|
tracksScroll: {
|
||||||
overflow: "visible",
|
overflow: "visible",
|
||||||
},
|
},
|
||||||
tracksScrollContent: {
|
tracksScrollContent: {
|
||||||
paddingHorizontal: 48,
|
paddingHorizontal: scaleSize(48),
|
||||||
paddingVertical: 8,
|
paddingVertical: scaleSize(8),
|
||||||
gap: 12,
|
gap: scaleSize(12),
|
||||||
},
|
},
|
||||||
trackCard: {
|
trackCard: {
|
||||||
width: 180,
|
width: scaleSize(180),
|
||||||
height: 80,
|
height: scaleSize(80),
|
||||||
borderRadius: 14,
|
borderRadius: scaleSize(14),
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: scaleSize(12),
|
||||||
},
|
},
|
||||||
trackCardText: {
|
trackCardText: {
|
||||||
fontSize: 16,
|
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
},
|
},
|
||||||
trackCardSublabel: {
|
trackCardSublabel: {
|
||||||
fontSize: 12,
|
marginTop: scaleSize(2),
|
||||||
marginTop: 2,
|
|
||||||
},
|
},
|
||||||
checkmark: {
|
checkmark: {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 8,
|
top: scaleSize(8),
|
||||||
right: 8,
|
right: scaleSize(8),
|
||||||
},
|
},
|
||||||
languageScroll: {
|
languageScroll: {
|
||||||
overflow: "visible",
|
overflow: "visible",
|
||||||
},
|
},
|
||||||
languageScrollContent: {
|
languageScrollContent: {
|
||||||
paddingHorizontal: 48,
|
paddingHorizontal: scaleSize(48),
|
||||||
paddingVertical: 8,
|
paddingVertical: scaleSize(8),
|
||||||
gap: 10,
|
gap: scaleSize(10),
|
||||||
},
|
},
|
||||||
languageCard: {
|
languageCard: {
|
||||||
width: 120,
|
width: scaleSize(120),
|
||||||
height: 60,
|
height: scaleSize(60),
|
||||||
borderRadius: 12,
|
borderRadius: scaleSize(12),
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: scaleSize(12),
|
||||||
},
|
},
|
||||||
languageCardText: {
|
languageCardText: {
|
||||||
fontSize: 15,
|
|
||||||
fontWeight: "500",
|
fontWeight: "500",
|
||||||
},
|
},
|
||||||
languageCardCode: {
|
languageCardCode: {
|
||||||
fontSize: 11,
|
marginTop: scaleSize(2),
|
||||||
marginTop: 2,
|
|
||||||
},
|
},
|
||||||
resultsScroll: {
|
resultsScroll: {
|
||||||
overflow: "visible",
|
overflow: "visible",
|
||||||
},
|
},
|
||||||
resultsScrollContent: {
|
resultsScrollContent: {
|
||||||
paddingHorizontal: 48,
|
paddingHorizontal: scaleSize(48),
|
||||||
paddingVertical: 8,
|
paddingVertical: scaleSize(8),
|
||||||
gap: 12,
|
gap: scaleSize(12),
|
||||||
},
|
},
|
||||||
resultCard: {
|
resultCard: {
|
||||||
width: 220,
|
width: scaleSize(220),
|
||||||
height: 130,
|
height: scaleSize(130),
|
||||||
borderRadius: 14,
|
borderRadius: scaleSize(14),
|
||||||
padding: 14,
|
padding: scaleSize(14),
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
},
|
},
|
||||||
providerBadge: {
|
providerBadge: {
|
||||||
alignSelf: "flex-start",
|
alignSelf: "flex-start",
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: scaleSize(8),
|
||||||
paddingVertical: 3,
|
paddingVertical: scaleSize(3),
|
||||||
borderRadius: 6,
|
borderRadius: scaleSize(6),
|
||||||
marginBottom: 8,
|
marginBottom: scaleSize(8),
|
||||||
},
|
},
|
||||||
providerText: {
|
providerText: {
|
||||||
fontSize: 11,
|
|
||||||
fontWeight: "600",
|
fontWeight: "600",
|
||||||
textTransform: "uppercase",
|
textTransform: "uppercase",
|
||||||
letterSpacing: 0.5,
|
letterSpacing: 0.5,
|
||||||
},
|
},
|
||||||
resultName: {
|
resultName: {
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: "500",
|
fontWeight: "500",
|
||||||
marginBottom: 8,
|
marginBottom: scaleSize(8),
|
||||||
lineHeight: 18,
|
lineHeight: scaleSize(18),
|
||||||
},
|
},
|
||||||
resultMeta: {
|
resultMeta: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: 12,
|
gap: scaleSize(12),
|
||||||
marginBottom: 8,
|
marginBottom: scaleSize(8),
|
||||||
},
|
|
||||||
resultMetaText: {
|
|
||||||
fontSize: 12,
|
|
||||||
},
|
},
|
||||||
|
resultMetaText: {},
|
||||||
ratingContainer: {
|
ratingContainer: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: 3,
|
gap: scaleSize(3),
|
||||||
},
|
},
|
||||||
downloadCountContainer: {
|
downloadCountContainer: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: 3,
|
gap: scaleSize(3),
|
||||||
},
|
},
|
||||||
flagsContainer: {
|
flagsContainer: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
gap: 6,
|
gap: scaleSize(6),
|
||||||
flexWrap: "wrap",
|
flexWrap: "wrap",
|
||||||
},
|
},
|
||||||
flag: {
|
flag: {
|
||||||
paddingHorizontal: 6,
|
paddingHorizontal: scaleSize(6),
|
||||||
paddingVertical: 2,
|
paddingVertical: scaleSize(2),
|
||||||
borderRadius: 4,
|
borderRadius: scaleSize(4),
|
||||||
},
|
},
|
||||||
flagText: {
|
flagText: {
|
||||||
fontSize: 10,
|
|
||||||
fontWeight: "600",
|
fontWeight: "600",
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
},
|
},
|
||||||
downloadingOverlay: {
|
downloadingOverlay: {
|
||||||
...StyleSheet.absoluteFillObject,
|
...StyleSheet.absoluteFillObject,
|
||||||
backgroundColor: "rgba(0,0,0,0.5)",
|
backgroundColor: "rgba(0,0,0,0.5)",
|
||||||
borderRadius: 14,
|
borderRadius: scaleSize(14),
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
loadingContainer: {
|
loadingContainer: {
|
||||||
paddingVertical: 20,
|
paddingVertical: scaleSize(20),
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
errorContainer: {
|
errorContainer: {
|
||||||
paddingVertical: 40,
|
paddingVertical: scaleSize(40),
|
||||||
paddingHorizontal: 48,
|
paddingHorizontal: scaleSize(48),
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
errorText: {
|
errorText: {
|
||||||
color: "rgba(255,100,100,0.9)",
|
color: "rgba(255,100,100,0.9)",
|
||||||
marginTop: 8,
|
marginTop: scaleSize(8),
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: "500",
|
fontWeight: "500",
|
||||||
},
|
},
|
||||||
errorHint: {
|
errorHint: {
|
||||||
color: "rgba(255,255,255,0.5)",
|
color: "rgba(255,255,255,0.5)",
|
||||||
marginTop: 4,
|
marginTop: scaleSize(4),
|
||||||
fontSize: 13,
|
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
},
|
},
|
||||||
emptyContainer: {
|
emptyContainer: {
|
||||||
paddingVertical: 40,
|
paddingVertical: scaleSize(40),
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
emptyText: {
|
emptyText: {
|
||||||
color: "rgba(255,255,255,0.5)",
|
color: "rgba(255,255,255,0.5)",
|
||||||
marginTop: 8,
|
marginTop: scaleSize(8),
|
||||||
fontSize: 14,
|
|
||||||
},
|
},
|
||||||
apiKeyHint: {
|
apiKeyHint: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: 8,
|
gap: scaleSize(8),
|
||||||
paddingHorizontal: 48,
|
paddingHorizontal: scaleSize(48),
|
||||||
paddingTop: 8,
|
paddingTop: scaleSize(8),
|
||||||
},
|
|
||||||
apiKeyHintText: {
|
|
||||||
color: "rgba(255,255,255,0.4)",
|
|
||||||
fontSize: 12,
|
|
||||||
},
|
},
|
||||||
|
apiKeyHintText: {},
|
||||||
// Settings tab styles
|
// Settings tab styles
|
||||||
settingsScroll: {
|
settingsScroll: {
|
||||||
maxHeight: 300,
|
maxHeight: scaleSize(300),
|
||||||
},
|
},
|
||||||
settingsScrollContent: {
|
settingsScrollContent: {
|
||||||
paddingHorizontal: 48,
|
paddingHorizontal: scaleSize(48),
|
||||||
paddingVertical: 8,
|
paddingVertical: scaleSize(8),
|
||||||
gap: 24,
|
gap: scaleSize(24),
|
||||||
},
|
},
|
||||||
settingRow: {
|
settingRow: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
@@ -1252,49 +1304,47 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
},
|
},
|
||||||
settingLabel: {
|
settingLabel: {
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: "500",
|
fontWeight: "500",
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
},
|
},
|
||||||
sizeControlContainer: {
|
sizeControlContainer: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: 16,
|
gap: scaleSize(16),
|
||||||
},
|
},
|
||||||
stepperButton: {
|
stepperButton: {
|
||||||
width: 56,
|
width: scaleSize(56),
|
||||||
height: 56,
|
height: scaleSize(56),
|
||||||
borderRadius: 14,
|
borderRadius: scaleSize(14),
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
sizeValueContainer: {
|
sizeValueContainer: {
|
||||||
width: 80,
|
width: scaleSize(80),
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
sizeValueText: {
|
sizeValueText: {
|
||||||
fontSize: 24,
|
|
||||||
fontWeight: "600",
|
fontWeight: "600",
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
|
fontSize: scaleSize(24),
|
||||||
},
|
},
|
||||||
alignmentRow: {
|
alignmentRow: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
gap: 10,
|
gap: scaleSize(10),
|
||||||
},
|
},
|
||||||
alignmentCard: {
|
alignmentCard: {
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: scaleSize(20),
|
||||||
paddingVertical: 14,
|
paddingVertical: scaleSize(14),
|
||||||
borderRadius: 12,
|
borderRadius: scaleSize(12),
|
||||||
minWidth: 90,
|
minWidth: scaleSize(90),
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
alignmentCardText: {
|
alignmentCardText: {
|
||||||
fontSize: 15,
|
|
||||||
textTransform: "capitalize",
|
textTransform: "capitalize",
|
||||||
},
|
},
|
||||||
alignmentCheckmark: {
|
alignmentCheckmark: {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: 6,
|
top: scaleSize(6),
|
||||||
right: 6,
|
right: scaleSize(6),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export const TVOptionCard = React.forwardRef<View, TVOptionCardProps>(
|
|||||||
fontWeight: focused || selected ? "600" : "400",
|
fontWeight: focused || selected ? "600" : "400",
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
}}
|
}}
|
||||||
numberOfLines={2}
|
numberOfLines={4}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export const TVSettingsStepper: React.FC<TVSettingsStepperProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Pressable
|
<Pressable
|
||||||
|
style={{ flex: 1 }}
|
||||||
onFocus={labelAnim.handleFocus}
|
onFocus={labelAnim.handleFocus}
|
||||||
onBlur={labelAnim.handleBlur}
|
onBlur={labelAnim.handleBlur}
|
||||||
hasTVPreferredFocus={isFirst && !disabled}
|
hasTVPreferredFocus={isFirst && !disabled}
|
||||||
|
|||||||
Reference in New Issue
Block a user