diff --git a/app/(auth)/tv-option-modal.tsx b/app/(auth)/tv-option-modal.tsx
index 330885a1..180228e3 100644
--- a/app/(auth)/tv-option-modal.tsx
+++ b/app/(auth)/tv-option-modal.tsx
@@ -1,6 +1,6 @@
import { BlurView } from "expo-blur";
import { useAtomValue } from "jotai";
-import { useEffect, useMemo, useRef, useState } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
Animated,
Easing,
@@ -11,13 +11,17 @@ import {
} from "react-native";
import { Text } from "@/components/common/Text";
import { TVOptionCard } from "@/components/tv";
+import { useScaledTVTypography } from "@/constants/TVTypography";
import useRouter from "@/hooks/useAppRouter";
+import { useTVBackPress } from "@/hooks/useTVBackPress";
import { tvOptionModalAtom } from "@/utils/atoms/tvOptionModal";
+import { scaleSize } from "@/utils/scaleSize";
import { store } from "@/utils/store";
export default function TVOptionModal() {
const router = useRouter();
const modalState = useAtomValue(tvOptionModalAtom);
+ const typography = useScaledTVTypography();
const [isReady, setIsReady] = useState(false);
const firstCardRef = useRef(null);
@@ -76,12 +80,25 @@ export default function TVOptionModal() {
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 (!modalState) {
return null;
}
- const { title, options, cardWidth = 160, cardHeight = 75 } = modalState;
+ const { title, options } = modalState;
+ const scaledCardWidth = scaleSize(160);
+ const scaledCardHeight = scaleSize(75);
return (
@@ -100,7 +117,9 @@ export default function TVOptionModal() {
trapFocusRight
style={styles.content}
>
- {title}
+
+ {title}
+
{isReady && (
handleSelect(option.value)}
- width={cardWidth}
- height={cardHeight}
+ width={scaledCardWidth}
+ height={scaledCardHeight}
/>
))}
@@ -142,21 +161,20 @@ const styles = StyleSheet.create({
width: "100%",
},
blurContainer: {
- borderTopLeftRadius: 24,
- borderTopRightRadius: 24,
+ borderTopLeftRadius: scaleSize(24),
+ borderTopRightRadius: scaleSize(24),
overflow: "hidden",
},
content: {
- paddingTop: 24,
- paddingBottom: 50,
+ paddingTop: scaleSize(24),
+ paddingBottom: scaleSize(50),
overflow: "visible",
},
title: {
- fontSize: 18,
fontWeight: "500",
color: "rgba(255,255,255,0.6)",
- marginBottom: 16,
- paddingHorizontal: 48,
+ marginBottom: scaleSize(16),
+ paddingHorizontal: scaleSize(48),
textTransform: "uppercase",
letterSpacing: 1,
},
@@ -164,8 +182,8 @@ const styles = StyleSheet.create({
overflow: "visible",
},
scrollContent: {
- paddingHorizontal: 48,
- paddingVertical: 20,
- gap: 12,
+ paddingHorizontal: scaleSize(48),
+ paddingVertical: scaleSize(20),
+ gap: scaleSize(12),
},
});
diff --git a/app/(auth)/tv-subtitle-modal.tsx b/app/(auth)/tv-subtitle-modal.tsx
index e597a782..c59952ba 100644
--- a/app/(auth)/tv-subtitle-modal.tsx
+++ b/app/(auth)/tv-subtitle-modal.tsx
@@ -22,14 +22,17 @@ import {
import { Text } from "@/components/common/Text";
import { TVTabButton, useTVFocusAnimation } from "@/components/tv";
import type { Track } from "@/components/video-player/controls/types";
+import { useScaledTVTypography } from "@/constants/TVTypography";
import useRouter from "@/hooks/useAppRouter";
import {
type SubtitleSearchResult,
useRemoteSubtitles,
} from "@/hooks/useRemoteSubtitles";
+import { useTVBackPress } from "@/hooks/useTVBackPress";
import { useSettings } from "@/utils/atoms/settings";
import { tvSubtitleModalAtom } from "@/utils/atoms/tvSubtitleModal";
import { COMMON_SUBTITLE_LANGUAGES } from "@/utils/opensubtitles/api";
+import { scaleSize } from "@/utils/scaleSize";
import { store } from "@/utils/store";
type TabType = "tracks" | "download" | "settings";
@@ -72,10 +75,10 @@ const TVTrackCard = React.forwardRef<
{label}
@@ -83,7 +86,10 @@ const TVTrackCard = React.forwardRef<
@@ -94,7 +100,7 @@ const TVTrackCard = React.forwardRef<
@@ -142,7 +148,7 @@ const LanguageCard = React.forwardRef<
{code.toUpperCase()}
@@ -161,7 +170,7 @@ const LanguageCard = React.forwardRef<
@@ -219,7 +228,10 @@ const SubtitleResultCard = React.forwardRef<
{result.providerName}
@@ -228,7 +240,10 @@ const SubtitleResultCard = React.forwardRef<
{/* Name */}
{result.name}
@@ -240,7 +255,10 @@ const SubtitleResultCard = React.forwardRef<
{result.format?.toUpperCase()}
@@ -252,7 +270,7 @@ const SubtitleResultCard = React.forwardRef<
@@ -275,7 +294,7 @@ const SubtitleResultCard = React.forwardRef<
@@ -307,7 +327,9 @@ const SubtitleResultCard = React.forwardRef<
},
]}
>
- Hash Match
+
+ Hash Match
+
)}
{result.hearingImpaired && (
@@ -323,7 +345,7 @@ const SubtitleResultCard = React.forwardRef<
>
@@ -339,7 +361,9 @@ const SubtitleResultCard = React.forwardRef<
},
]}
>
- AI
+
+ AI
+
)}
@@ -389,7 +413,7 @@ const TVStepperButton: React.FC<{
>
@@ -485,7 +509,7 @@ const TVAlignmentCard: React.FC<{
@@ -495,7 +519,7 @@ const TVAlignmentCard: React.FC<{
@@ -510,6 +534,7 @@ export default function TVSubtitleModal() {
const { t } = useTranslation();
const modalState = useAtomValue(tvSubtitleModalAtom);
const { settings, updateSettings } = useSettings();
+ const typography = useScaledTVTypography();
const [activeTab, setActiveTab] = useState("tracks");
const [selectedLanguage, setSelectedLanguage] = useState("eng");
@@ -604,6 +629,12 @@ export default function TVSubtitleModal() {
router.back();
}, [router]);
+ // Intercept back/menu press to close the modal instead of the player
+ useTVBackPress(() => {
+ handleClose();
+ return true;
+ }, [handleClose]);
+
const handleLanguageSelect = useCallback(
(code: string) => {
setSelectedLanguage(code);
@@ -745,7 +776,7 @@ export default function TVSubtitleModal() {
>
{/* Header with tabs */}
-
+
{t("item_card.subtitles.label") || "Subtitles"}
@@ -802,7 +833,9 @@ export default function TVSubtitleModal() {
<>
{/* Language Selector */}
-
+
{t("player.language") || "Language"}
-
+
{t("player.results") || "Results"}
{searchResults && ` (${searchResults.length})`}
@@ -846,13 +881,17 @@ export default function TVSubtitleModal() {
-
+
{t("player.search_failed") || "Search failed"}
-
+
{!hasOpenSubtitlesApiKey
? t("player.no_subtitle_provider") ||
"No subtitle provider configured on server"
@@ -869,10 +908,15 @@ export default function TVSubtitleModal() {
-
+
{t("player.no_subtitles_found") ||
"No subtitles found"}
@@ -907,10 +951,15 @@ export default function TVSubtitleModal() {
-
+
{t("player.add_opensubtitles_key_hint") ||
"Add OpenSubtitles API key in settings for client-side fallback"}
@@ -942,7 +991,12 @@ export default function TVSubtitleModal() {
}}
hasTVPreferredFocus={true}
/>
-
+
{t("home.settings.subtitles.mpv_subtitle_scale") ||
"Subtitle Scale"}
@@ -960,7 +1014,12 @@ export default function TVSubtitleModal() {
updateSettings({ mpvSubtitleMarginY: newValue });
}}
/>
-
+
{t("home.settings.subtitles.mpv_subtitle_margin_y") ||
"Vertical Margin"}
@@ -984,7 +1043,12 @@ export default function TVSubtitleModal() {
/>
))}
-
+
{t("home.settings.subtitles.mpv_subtitle_align_x") ||
"Horizontal Align"}
@@ -1008,7 +1072,12 @@ export default function TVSubtitleModal() {
/>
))}
-
+
{t("home.settings.subtitles.mpv_subtitle_align_y") ||
"Vertical Align"}
@@ -1033,218 +1102,201 @@ const styles = StyleSheet.create({
maxHeight: "70%",
},
blurContainer: {
- borderTopLeftRadius: 24,
- borderTopRightRadius: 24,
+ borderTopLeftRadius: scaleSize(24),
+ borderTopRightRadius: scaleSize(24),
overflow: "hidden",
},
content: {
- paddingTop: 24,
- paddingBottom: 48,
+ paddingTop: scaleSize(24),
+ paddingBottom: scaleSize(48),
},
header: {
- paddingHorizontal: 48,
- marginBottom: 20,
+ paddingHorizontal: scaleSize(48),
+ marginBottom: scaleSize(20),
},
title: {
- fontSize: 24,
fontWeight: "600",
color: "#fff",
- marginBottom: 16,
+ marginBottom: scaleSize(16),
},
tabRow: {
flexDirection: "row",
- gap: 24,
+ gap: scaleSize(24),
},
section: {
- marginBottom: 20,
+ marginBottom: scaleSize(20),
},
sectionTitle: {
- fontSize: 14,
fontWeight: "500",
color: "rgba(255,255,255,0.5)",
textTransform: "uppercase",
letterSpacing: 1,
- marginBottom: 12,
- paddingHorizontal: 48,
+ marginBottom: scaleSize(12),
+ paddingHorizontal: scaleSize(48),
},
tracksScroll: {
overflow: "visible",
},
tracksScrollContent: {
- paddingHorizontal: 48,
- paddingVertical: 8,
- gap: 12,
+ paddingHorizontal: scaleSize(48),
+ paddingVertical: scaleSize(8),
+ gap: scaleSize(12),
},
trackCard: {
- width: 180,
- height: 80,
- borderRadius: 14,
+ width: scaleSize(180),
+ height: scaleSize(80),
+ borderRadius: scaleSize(14),
justifyContent: "center",
alignItems: "center",
- paddingHorizontal: 12,
+ paddingHorizontal: scaleSize(12),
},
trackCardText: {
- fontSize: 16,
textAlign: "center",
},
trackCardSublabel: {
- fontSize: 12,
- marginTop: 2,
+ marginTop: scaleSize(2),
},
checkmark: {
position: "absolute",
- top: 8,
- right: 8,
+ top: scaleSize(8),
+ right: scaleSize(8),
},
languageScroll: {
overflow: "visible",
},
languageScrollContent: {
- paddingHorizontal: 48,
- paddingVertical: 8,
- gap: 10,
+ paddingHorizontal: scaleSize(48),
+ paddingVertical: scaleSize(8),
+ gap: scaleSize(10),
},
languageCard: {
- width: 120,
- height: 60,
- borderRadius: 12,
+ width: scaleSize(120),
+ height: scaleSize(60),
+ borderRadius: scaleSize(12),
justifyContent: "center",
alignItems: "center",
- paddingHorizontal: 12,
+ paddingHorizontal: scaleSize(12),
},
languageCardText: {
- fontSize: 15,
fontWeight: "500",
},
languageCardCode: {
- fontSize: 11,
- marginTop: 2,
+ marginTop: scaleSize(2),
},
resultsScroll: {
overflow: "visible",
},
resultsScrollContent: {
- paddingHorizontal: 48,
- paddingVertical: 8,
- gap: 12,
+ paddingHorizontal: scaleSize(48),
+ paddingVertical: scaleSize(8),
+ gap: scaleSize(12),
},
resultCard: {
- width: 220,
- height: 130,
- borderRadius: 14,
- padding: 14,
+ width: scaleSize(220),
+ height: scaleSize(130),
+ borderRadius: scaleSize(14),
+ padding: scaleSize(14),
borderWidth: 1,
overflow: "hidden",
},
providerBadge: {
alignSelf: "flex-start",
- paddingHorizontal: 8,
- paddingVertical: 3,
- borderRadius: 6,
- marginBottom: 8,
+ paddingHorizontal: scaleSize(8),
+ paddingVertical: scaleSize(3),
+ borderRadius: scaleSize(6),
+ marginBottom: scaleSize(8),
},
providerText: {
- fontSize: 11,
fontWeight: "600",
textTransform: "uppercase",
letterSpacing: 0.5,
},
resultName: {
- fontSize: 14,
fontWeight: "500",
- marginBottom: 8,
- lineHeight: 18,
+ marginBottom: scaleSize(8),
+ lineHeight: scaleSize(18),
},
resultMeta: {
flexDirection: "row",
alignItems: "center",
- gap: 12,
- marginBottom: 8,
- },
- resultMetaText: {
- fontSize: 12,
+ gap: scaleSize(12),
+ marginBottom: scaleSize(8),
},
+ resultMetaText: {},
ratingContainer: {
flexDirection: "row",
alignItems: "center",
- gap: 3,
+ gap: scaleSize(3),
},
downloadCountContainer: {
flexDirection: "row",
alignItems: "center",
- gap: 3,
+ gap: scaleSize(3),
},
flagsContainer: {
flexDirection: "row",
- gap: 6,
+ gap: scaleSize(6),
flexWrap: "wrap",
},
flag: {
- paddingHorizontal: 6,
- paddingVertical: 2,
- borderRadius: 4,
+ paddingHorizontal: scaleSize(6),
+ paddingVertical: scaleSize(2),
+ borderRadius: scaleSize(4),
},
flagText: {
- fontSize: 10,
fontWeight: "600",
color: "#fff",
},
downloadingOverlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: "rgba(0,0,0,0.5)",
- borderRadius: 14,
+ borderRadius: scaleSize(14),
justifyContent: "center",
alignItems: "center",
},
loadingContainer: {
- paddingVertical: 20,
+ paddingVertical: scaleSize(20),
alignItems: "center",
},
errorContainer: {
- paddingVertical: 40,
- paddingHorizontal: 48,
+ paddingVertical: scaleSize(40),
+ paddingHorizontal: scaleSize(48),
alignItems: "center",
},
errorText: {
color: "rgba(255,100,100,0.9)",
- marginTop: 8,
- fontSize: 16,
+ marginTop: scaleSize(8),
fontWeight: "500",
},
errorHint: {
color: "rgba(255,255,255,0.5)",
- marginTop: 4,
- fontSize: 13,
+ marginTop: scaleSize(4),
textAlign: "center",
},
emptyContainer: {
- paddingVertical: 40,
+ paddingVertical: scaleSize(40),
alignItems: "center",
},
emptyText: {
color: "rgba(255,255,255,0.5)",
- marginTop: 8,
- fontSize: 14,
+ marginTop: scaleSize(8),
},
apiKeyHint: {
flexDirection: "row",
alignItems: "center",
- gap: 8,
- paddingHorizontal: 48,
- paddingTop: 8,
- },
- apiKeyHintText: {
- color: "rgba(255,255,255,0.4)",
- fontSize: 12,
+ gap: scaleSize(8),
+ paddingHorizontal: scaleSize(48),
+ paddingTop: scaleSize(8),
},
+ apiKeyHintText: {},
// Settings tab styles
settingsScroll: {
- maxHeight: 300,
+ maxHeight: scaleSize(300),
},
settingsScrollContent: {
- paddingHorizontal: 48,
- paddingVertical: 8,
- gap: 24,
+ paddingHorizontal: scaleSize(48),
+ paddingVertical: scaleSize(8),
+ gap: scaleSize(24),
},
settingRow: {
flexDirection: "row",
@@ -1252,49 +1304,47 @@ const styles = StyleSheet.create({
justifyContent: "space-between",
},
settingLabel: {
- fontSize: 18,
fontWeight: "500",
color: "#fff",
},
sizeControlContainer: {
flexDirection: "row",
alignItems: "center",
- gap: 16,
+ gap: scaleSize(16),
},
stepperButton: {
- width: 56,
- height: 56,
- borderRadius: 14,
+ width: scaleSize(56),
+ height: scaleSize(56),
+ borderRadius: scaleSize(14),
justifyContent: "center",
alignItems: "center",
},
sizeValueContainer: {
- width: 80,
+ width: scaleSize(80),
alignItems: "center",
},
sizeValueText: {
- fontSize: 24,
fontWeight: "600",
color: "#fff",
+ fontSize: scaleSize(24),
},
alignmentRow: {
flexDirection: "row",
- gap: 10,
+ gap: scaleSize(10),
},
alignmentCard: {
- paddingHorizontal: 20,
- paddingVertical: 14,
- borderRadius: 12,
- minWidth: 90,
+ paddingHorizontal: scaleSize(20),
+ paddingVertical: scaleSize(14),
+ borderRadius: scaleSize(12),
+ minWidth: scaleSize(90),
alignItems: "center",
},
alignmentCardText: {
- fontSize: 15,
textTransform: "capitalize",
},
alignmentCheckmark: {
position: "absolute",
- top: 6,
- right: 6,
+ top: scaleSize(6),
+ right: scaleSize(6),
},
});
diff --git a/components/tv/TVOptionCard.tsx b/components/tv/TVOptionCard.tsx
index 525a7b62..200f2a9f 100644
--- a/components/tv/TVOptionCard.tsx
+++ b/components/tv/TVOptionCard.tsx
@@ -68,7 +68,7 @@ export const TVOptionCard = React.forwardRef(
fontWeight: focused || selected ? "600" : "400",
textAlign: "center",
}}
- numberOfLines={2}
+ numberOfLines={4}
>
{label}
diff --git a/components/tv/settings/TVSettingsStepper.tsx b/components/tv/settings/TVSettingsStepper.tsx
index c63430be..d0ba7b2c 100644
--- a/components/tv/settings/TVSettingsStepper.tsx
+++ b/components/tv/settings/TVSettingsStepper.tsx
@@ -49,6 +49,7 @@ export const TVSettingsStepper: React.FC = ({
}}
>